Version: 1.0.0
Released: May 2026
Verified on: Windows 11 23H2 and Windows Server 2022 (both Workgroup) ✅
Other supported OS: Windows 10 22H2, Windows Server 2019 / 2025, Windows 11 24H2
| Requirement | Why it matters | How to check |
|---|---|---|
| Windows 10 22H2, Windows 11 23H2 / 24H2, Windows Server 2019 / 2022 / 2025 | Credential Provider API + KERB_INTERACTIVE_UNLOCK_LOGON struct are stable across these. Older Windows (7, 8, Server 2016) ships an older CP API and is not supported. | winver or (Get-CimInstance Win32_OperatingSystem).Caption |
| 64-bit (x64) architecture | The MSI ships only x64 binaries. ARM64 and x86 Windows are not supported in v1.0.0. | [Environment]::Is64BitOperatingSystem must return True |
| Administrator rights at install time | MSI writes to HKLM\SOFTWARE\Classes\CLSID\…, registers a Credential Provider, and writes to C:\Program Files\…. All require admin. | Run PowerShell as Administrator |
| At least 50 MB free disk on system drive | Install is ~3 MB but DPAPI working files, trust.db, and logs grow over time. | (Get-PSDrive C).Free > 50 MB |
Unsupported OS: the MSI may install (no version gate enforced), but the Credential Provider may not load, the tile may not render, or sign-in may crash LogonUI on unsupported OSes. Do not deploy to production on unsupported Windows.
dotnet --list-runtimes8.0.x):Microsoft.WindowsDesktop.App 8.0.27 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]winget install Microsoft.DotNet.DesktopRuntime.8 `
--accept-source-agreements --accept-package-agreements.NET Desktop Runtime 8.0.x → Windows x64 Installer → run.dotnet --list-runtimes.Why specifically the Desktop Runtime? The Configurator is a WPF app. The Credential Provider's bridge loads Microsoft.WindowsDesktop.Appinside LogonUI. The "ASP.NET Core" or "Server" variants of .NET 8 are not sufficient and will produce silent load failures.
$pw = Read-Host -AsSecureString 'Password for rescue admin (write it down!)'
New-LocalUser -Name 'gk-rescue' -Password $pw `
-PasswordNeverExpires `
-Description 'Authyo RDP Guard emergency rescue admin'
Add-LocalGroupMember -Group 'Administrators' -Member 'gk-rescue'gk-rescue with the password you set.If the rescue admin sign-in fails — do not install the MSI until you fix it. You have no safety net otherwise.
Administrators group# DNS resolution
Resolve-DnsName app.authyo.io
# HTTPS reachability
$r = Invoke-WebRequest 'https://app.authyo.io/' -UseBasicParsing -TimeoutSec 5
"HTTP status: $($r.StatusCode)" # expect 200 (or 404 — both prove reachability)config.json → authyo.timeoutSeconds.System.Net.HttpClient which honors the system proxy (netsh winhttp show proxy). For most corporate proxies the default behavior is fine. For proxies that require client certificates or NTLM auth — contact support@authyo.io.(Get-CimInstance -Class Win32_TSGeneralSetting `
-Namespace root\cimv2\TerminalServices `
-Filter "TerminalName='RDP-tcp'").UserAuthenticationRequired
# 1 = NLA on (bypasses Authyo)
# 0 = NLA off (Authyo tile fires)(Get-WmiObject -class Win32_TSGeneralSetting `
-Namespace root\cimv2\TerminalServices `
-Filter "TerminalName='RDP-tcp'").SetUserAuthenticationRequired(0)policy.blockOnAuthyoUnreachable = true in config.json (the default) — fail-closed if Authyo is unreachable.Get-NetFirewallRule -DisplayGroup 'Remote Desktop' |
Select DisplayName, Enabled, ProfileEnabled = True for the profile where your clients connect from.HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp\PortNumber without first adding a Windows Firewall inbound rule for the new port locks RDP out. If you want to move RDP off 3389:# 1. Add the firewall rule for the new port FIRST
New-NetFirewallRule -DisplayName 'RDP (custom port 33899)' `
-Direction Inbound -Protocol TCP -LocalPort 33899 -Action Allow
# 2. THEN change the port
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' `
-Name PortNumber -Value 33899
# 3. Test from a SECOND mstsc window BEFORE restarting TermService
mstsc /v:server-ip:33899
# 4. Once the new port works, restart the service
Restart-Service -Name TermService -Force| Requirement | Why | How to verify |
|---|---|---|
| Active Authyo application with a valid Client ID + Client Secret | Configurator wizard needs these to fetch OTPs. | Portal → Applications → confirm the app is Active and has both credentials. |
| Auth priority configured for every channel you'll use (Email / SMS / WhatsApp / Voice Call) | Backend rejects SendOTP if the application doesn't have an auth priority set for the channel. If you have 3 contacts with mixed channels, every channel needs its own priority. | In the app's settings, each channel must show Active = ON and Priority = 1 or higher. |
| One or more reachable contacts (real email inbox or real phone) | Without working contacts, OTPs are sent but never received. | Test with the Send test OTP button in the Configurator wizard. |
| Sufficient wallet balance in your Authyo account | Every OTP delivered debits your wallet (per-OTP, no subscription). If you use the AllInOrder policy with N contacts, each sign-in debits N OTPs. | Portal → Billing → wallet balance. |
| No active OTP rate limits for the target contacts | Backend rate-limits SendOTP per (app, contact) pair to prevent abuse. | If you hit a limit during testing, wait ~30 seconds and retry. |
Install-WindowsFeature Windows-Server-Backup -IncludeManagementTools
# Then Server Manager → Tools → Windows Server Backup → Backup Once → System State# ============================================================
# Authyo RDP Guard — pre-install self-check
# Paste this whole block into an elevated PowerShell window.
# ============================================================
$results = New-Object System.Collections.ArrayList
function Test-Item {
param([string]$Name, [scriptblock]$Check)
try {
$ok, $detail = & $Check
$results.Add([PSCustomObject]@{
Item = $Name
Status = if ($ok) { 'PASS' } else { 'FAIL' }
Detail = $detail
}) | Out-Null
} catch {
$results.Add([PSCustomObject]@{
Item = $Name; Status = 'FAIL'; Detail = "ERROR: $_"
}) | Out-Null
}
}
# 1. OS & arch
Test-Item 'Supported Windows version' {
$caption = (Get-CimInstance Win32_OperatingSystem).Caption
$build = [int](Get-CimInstance Win32_OperatingSystem).BuildNumber
@($build -ge 17763), "$caption (build $build)"
}
Test-Item '64-bit OS' {
@([Environment]::Is64BitOperatingSystem),
"x64: $([Environment]::Is64BitOperatingSystem)"
}
Test-Item 'Administrator rights' {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$isAdmin = (New-Object Security.Principal.WindowsPrincipal($id)).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
@($isAdmin), "Elevated: $isAdmin"
}
Test-Item 'Free disk > 50 MB on C:' {
$free = (Get-PSDrive C).Free / 1MB
@($free -gt 50), "$([math]::Round($free, 0)) MB free"
}
# 2. .NET 8 Desktop Runtime
Test-Item '.NET 8 Desktop Runtime' {
$runtimes = (& dotnet --list-runtimes 2>$null) -join "`n"
$has = $runtimes -match 'Microsoft\.WindowsDesktop\.App\s+8\.'
@($has), $(if ($has) { 'WindowsDesktop.App 8.x present' } else { 'MISSING - install from https://dotnet.microsoft.com/download/dotnet/8.0' })
}
# 3. Rescue admin
Test-Item 'At least 2 local administrators' {
$adminCount = (Get-LocalGroupMember Administrators |
Where-Object { $_.PrincipalSource -eq 'Local' }).Count
@($adminCount -ge 2), "$adminCount local admin(s) - recommend gk-rescue as a 2nd account"
}
# 4. Network reachability
Test-Item 'DNS resolves app.authyo.io' {
try {
$null = Resolve-DnsName app.authyo.io -ErrorAction Stop
@($true), 'OK'
} catch { @($false), "DNS failure: $_" }
}
Test-Item 'HTTPS reachable to app.authyo.io' {
try {
$r = Invoke-WebRequest 'https://app.authyo.io/' `
-UseBasicParsing -TimeoutSec 5 -ErrorAction Stop
@($true), "HTTP $($r.StatusCode)"
} catch {
if ($_.Exception.Response) {
@($true), "HTTP $([int]$_.Exception.Response.StatusCode) (reachable)"
} else {
@($false), "Unreachable: $($_.Exception.Message)"
}
}
}
# 5. RDP / NLA
Test-Item 'NLA disabled for RDP (required for MFA gate)' {
$nla = (Get-CimInstance -Class Win32_TSGeneralSetting `
-Namespace root\cimv2\TerminalServices `
-Filter "TerminalName='RDP-tcp'").UserAuthenticationRequired
$msg = if ($nla -eq 0) {
'NLA off - Authyo tile will fire on RDP'
} else {
"NLA ON - RDP will BYPASS Authyo OTP gate. Disable with: (Get-WmiObject Win32_TSGeneralSetting -Namespace root\cimv2\TerminalServices -Filter `"TerminalName='RDP-tcp'`").SetUserAuthenticationRequired(0)"
}
@($nla -eq 0), $msg
}
Test-Item 'Remote Desktop service running' {
$svc = Get-Service TermService -ErrorAction SilentlyContinue
@($svc -and $svc.Status -eq 'Running'),
$(if ($svc) { $svc.Status } else { 'Service missing' })
}
# Summary
Write-Host ''
Write-Host '=== Authyo RDP Guard pre-install self-check ===' -ForegroundColor Cyan
$results | Format-Table Item, Status, Detail -AutoSize -Wrap
$failed = ($results | Where-Object Status -eq 'FAIL').Count
if ($failed -eq 0) {
Write-Host "ALL CHECKS PASSED - safe to install the MSI." -ForegroundColor Green
} else {
Write-Host "$failed CHECK(S) FAILED - fix above before installing." -ForegroundColor Red
}AuthyoGatekeeper-1.0.0.msi (~1.3 MB).| Path | Purpose |
|---|---|
C:\Program Files\Authyo\Gatekeeper\Authyo.Gatekeeper.CredentialProvider.dll | The Credential Provider that adds the Authyo tile to the logon screen |
C:\Program Files\Authyo\Gatekeeper\Authyo.Gatekeeper.Bridge.dll | C++/CLI shim that hosts the .NET 8 runtime inside LogonUI |
C:\Program Files\Authyo\Gatekeeper\Authyo.Gatekeeper.Core.dll | Managed library — HTTP client, JWT, DPAPI, SQLite trust cache |
C:\Program Files\Authyo\Gatekeeper\Authyo.Gatekeeper.Configurator.exe | WPF wizard for setting up Client ID + Secret + contacts |
C:\Program Files\Authyo\Gatekeeper\Ijwhost.dll + SQLite libs | .NET 8 host + dependencies |
C:\ProgramData\Authyo\Gatekeeper\ | Where config.json, trust.db, and logs live after first run |
| Key | Purpose |
|---|---|
HKLM\SOFTWARE\Classes\CLSID\{B251F0AF-6D20-4C4D-90ED-4ADE0D8256D7} | COM registration of the Credential Provider |
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{B251F0AF-...} | Tells LogonUI to load the CP |
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Provider Filters\{B251F0AF-...} | Filter registration (enforces MFA for RDP) |
C:\Program Files\Authyo\Gatekeeper\)| Field | What goes here |
|---|---|
| Client ID | From step 4.1 |
| Client Secret | From step 4.1. Encrypted with Windows DPAPI before being saved — it never sits on disk in plaintext. |
| API endpoint | https://app.authyo.io (already pre-filled, just confirm) |
| Column | Notes |
|---|---|
| Name | A friendly label (e.g. "Naveen", "On-call rotation", "Backup"). Helps you remember which entry is which. |
| Recipient | The email address or phone number (with country code, no + or spaces — e.g. 919876543210) where the OTP goes. |
| Channel | Email, Sms, Whatsapp, or VoiceCall. Most setups start with Email — free and reliable. |
| ✕ button | Remove this contact. The form always keeps at least one row visible. |
| Policy | What happens on each sign-in | Wallet cost per sign-in |
|---|---|---|
| PrimaryOnly | OTP only goes to the first contact in the list. Other rows are listed but unused (they're inactive backups you can promote later). | 1 OTP |
| RoundRobin | OTP rotates: sign-in #1 → contact[0], sign-in #2 → contact[1], etc. Wraps around. Shares the load across multiple admins. State file lives at C:\ProgramData\Authyo\Gatekeeper\rr-state and persists across reboots. | 1 OTP |
| AllInOrder | OTP goes to every contact at the same time. All admins get the same code. Any one of them can share the OTP with the user. Best for redundancy. | N OTPs (N = contact count) |
PrimaryOnly (default)RoundRobin (rotates without anyone getting overwhelmed)AllInOrder (any admin can approve, never blocked if one is unreachable)| Field | What goes here |
|---|---|
| Hours (0–24) | 0 = always require OTP (most secure). Higher value = skip OTP for repeat sign-ins from the same IP + user within that window. Recommended 0 for production. |
Saved to C:\ProgramData\Authyo\Gatekeeper\config.json. 3 contact(s), policy=AllInOrder. Lock the screen (Win+L) to see the Authyo RDP Guard tile.
Get-Content 'C:\ProgramData\Authyo\Gatekeeper\logs\gatekeeper.log' -Tail 10INFO user=naveen ip=console GATE_START
INFO user=naveen ip=console SENDOTP_OK contact='Primary' via=Email to=nav***com maskId=...
INFO user=naveen ip=console JWT_VERIFIED
INFO user=naveen ip=console GATE_PASSGATE_PASS is the final success marker. If you see that line every time you sign in, Authyo RDP Guard is working correctly.mstsc and connects, the server's logon screen renders inside the RDP window — and your Authyo tile appears there exactly like it does on the physical console.(Get-WmiObject -class Win32_TSGeneralSetting `
-Namespace root\cimv2\TerminalServices `
-Filter "TerminalName='RDP-tcp'").SetUserAuthenticationRequired(0)mstsc /v:your-server.example.comCPUS_UNLOCK_WORKSTATION, which our tile handles identically to first connect). Each reconnect = another OTP. This is the intended security behavior.What about an OTP prompt at the client's mstscstep (before connecting)? That'sCPUS_CREDUI— a separate scenario that fires on the client side. v1.0.0 doesn't register for it; the OTP gate only runs once you reach the server's logon. This is deferred to v1.1 because it shifts the OTP from the protected server to the (potentially untrusted) client.
C:\ProgramData\Authyo\Gatekeeper\config.json. When the config is missing, the Authyo tile silently hides and users sign in via the standard Windows password tile. Restore the config to re-enable.| File | What's in it |
|---|---|
C:\ProgramData\Authyo\Gatekeeper\logs\gatekeeper.log | Plain-text log of every sign-in attempt: GATE_START, SENDOTP_OK/FAIL, JWT_VERIFIED, GATE_PASS |
| Windows Event Viewer → Application | Anything that fails before the Authyo log is initialized — useful if the Credential Provider itself can't load |
config.json is missing or corrupted.# Confirm the config exists and is readable
Get-Content 'C:\ProgramData\Authyo\Gatekeeper\config.json'
# Confirm the Credential Provider is registered
$clsid = '{B251F0AF-6D20-4C4D-90ED-4ADE0D8256D7}'
Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\$clsid"dotnet --list-runtimes and confirm the Microsoft.WindowsDesktop.App 8.0.x entry is there.AllInOrder policy and only some admins receive the OTP:gatekeeper.log — SENDOTP_FAIL lines tell you which contact's send failed and whyapp.authyo.io. Confirm:app.authyo.io (try nslookup app.authyo.io from an admin PowerShell)config.json — not recommended for production.Compress-Archive `
-Path 'C:\ProgramData\Authyo\Gatekeeper\logs',
'C:\ProgramData\Authyo\Gatekeeper\config.json' `
-DestinationPath "C:\authyo-diagnostics-$(Get-Date -Format yyyyMMdd-HHmmss).zip"clientSecretEncrypted blob is DPAPI-encrypted, so it's safe to share — nobody but the machine that created it can decrypt it).| Symptom | Most likely cause | First thing to check |
|---|---|---|
| MSI installs but Authyo tile doesn't appear | .NET 8 Desktop Runtime missing | dotnet --list-runtimes |
| Tile appears but says "Cannot reach Authyo" | DNS / HTTPS / firewall blocking app.authyo.io | Invoke-WebRequest https://app.authyo.io/ -UseBasicParsing |
| Tile shows "auth priority not set" | Authyo application missing auth priority for one of the channels | Portal → app settings → enable the channel + set priority > 0 |
| Some channels work, others fail | Per-channel auth priority — Email may be configured but SMS isn't | Configure auth priority for every channel you list in contacts |
| Email never arrives | Wrong contact, rate limit, or auth priority issue | Check gatekeeper.log for SENDOTP_OK vs SENDOTP_FAIL |
| OTP entered but says "OTP did not verify" | Wrong code, expired (5 min), or backend lockout | Wait 30s, retry; retry budget is 5 wrong before lockout |
| OTP verifies but "username or password incorrect" | LSA rejected the Windows password | Try same credentials on the standard Windows password tile |
| RDP signs in WITHOUT OTP | NLA is enabled — pre-authenticates before Authyo loads | Disable NLA — section 2.5.1 |
| RDP shows the tile but stuck at logon | Network kill during OTP step + blockOnAuthyoUnreachable=true | Check gatekeeper.log; resolve network; or temporarily set blockOnAuthyoUnreachable=false |
| Tile gone after install, no Authyo in Add/Remove | DPAPI machine key rotated; config.json undecryptable | Re-run Configurator and re-save |
| Wallet drains fast | AllInOrder policy with many contacts × frequent sign-ins | Switch to PrimaryOnly or RoundRobin if cost is a concern |
| Customer locked out, no rescue admin | Worst case — needs console / safe-mode boot | See Disaster recovery below |
reg delete HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{B251F0AF-6D20-4C4D-90ED-4ADE0D8256D7} /f → reboot. (Wipes the CP registration; reinstall when ready.)$app = Get-WmiObject Win32_Product -Filter "Name='Authyo RDP Guard'"
$app.Uninstall()C:\Program Files\Authyo\Gatekeeper\, all four registry entries, the Start Menu shortcut, and the Add/Remove Programs entry.C:\ProgramData\Authyo\Gatekeeper\ (your config.json and trust.db). For a full purge:Remove-Item 'C:\ProgramData\Authyo' -Recurse -Force(Get-WmiObject -class Win32_TSGeneralSetting `
-Namespace root\cimv2\TerminalServices `
-Filter "TerminalName='RDP-tcp'").SetUserAuthenticationRequired(1)app.authyo.io, TLS 1.2+, server-cert validated by the default Windows trust store.success: true.trust.db is a SQLite file storing source-IP + username fingerprints (no passwords, no tokens). Bounded TTL, defaults to 0 (always require OTP).AllInOrder, every contact gets the same OTP (same maskId), so any one admin can share the code. This is delivery redundancy, not multi-party approval.LICENSE in the install directory.