Windows Server for beginners. Part 26: How do NPS and RADIUS work?

All parts of the series “Windows Server for beginners”

A Wi-Fi access point, a switch with ports in the meeting rooms, a VPN gateway at the network edge — they all have one job: let your own people in and keep strangers out. None of them has a user database, nor should it: users live in the directory. Between the network equipment and the directory you need a translator that takes a request "here is a person with this name and this proof, let them in?" and answers "yes, into this VLAN" or "no". That translator is RADIUS, a protocol that appeared in 1991 at a maker of modem pools and outlived everything that was supposed to replace it, and in Windows Server it is implemented by the Network Policy Server role.

This part is about the protocol and the server; the next two are about its two main uses, Wi-Fi and VPN.


RADIUS

Remote Authentication Dial-In User Service, RFC 2865 (authentication and authorization) and 2866 (accounting). The participants are the NAS (network access server — an access point, a switch, a gateway; in RADIUS terms it is the client) and the RADIUS server. Between them runs UDP — no connections and no retransmission, so the NAS repeats a request itself if no answer comes. Port 1812 for authentication and 1813 for accounting; the first implementations took 1645 and 1646, and NPS listens on all four by default. The third thing that binds the two sides is the shared secret, a string both of them know.

The authentication packet types and their codes in the header: Access-Request (1), Access-Accept (2), Access-Reject (3), Access-Challenge (11):

 NAS                                     RADIUS server
  │                                           │
  ├─ Access-Request ─────────────────────────►│  User-Name, NAS-IP-Address, NAS-Port-Type,
  │                                           │  Calling-Station-Id (client MAC), Called-Station-Id
  │                                           │  (AP MAC:SSID), and the proof: User-Password
  │                                           │  or EAP-Message
  │◄─ Access-Challenge ───────────────────────┤  "ask again": the next EAP step; may repeat
  ├─ Access-Request ─────────────────────────►│  a dozen times
  │◄─ Access-Accept ──────────────────────────┤  let in; attributes: VLAN, timeouts, encryption keys
  │    or Access-Reject                       │  keep out; no explanation to the client

A packet is a header (code, identifier, length, a 16-byte Authenticator) and a list of attributes in type-length-value format; the maximum packet length is 4096 bytes, the data of one attribute at most 253 bytes. RFC 2865 defines 63 attributes, later RFCs added more, and type 26, Vendor-Specific, carries vendor attributes: Microsoft (MS-MPPE-Send-Key, MS-CHAP-*, RFC 2548), Cisco (cisco-av-pair) and so on.

Packet protection is the shared secret and the Authenticator. In a request the Authenticator is random, and the password in User-Password is hidden by XOR with an MD5 of the secret and the Authenticator (MD5 is a hash function that yields 16 bytes from any data); in a response the Authenticator is an MD5 of the whole response together with the request Authenticator and the secret, and the NAS uses it to check that the response is genuine. That was enough in the nineties; today it isn't. In 2024 the Blast-RADIUS attack was published: an attacker in the middle between the NAS and the server computes an MD5 collision and turns an Access-Reject into an Access-Accept. The defence is the Message-Authenticator attribute (type 80, RFC 2869): an HMAC-MD5 of the whole packet keyed with the secret, that is, a signature that can't be forged without the secret. For EAP it is mandatory under RFC 3579, and NPS requires it in every request with EAP; requests without EAP — PAP, MS-CHAP — are the vulnerable ones. Windows updates since July 2024 added checks to NPS, but they are off by default: Microsoft recommends requiring Message-Authenticator from every client (requireauthattrib) and turning on limitProxyState and requireMsgAuth (more on them below). The general rule stays the same: RADIUS doesn't cross untrusted networks, and if it has to, it goes inside IPsec (part 19 of the networking series). There is also RADIUS over TLS — RadSec (RFC 6614): the same protocol inside a TLS connection over TCP 2083, where the secret no longer matters — but NPS doesn't support it.

EAP inside RADIUS (RFC 3579). For Wi-Fi and wired 802.1X the proof isn't a password in an attribute but a conversation over EAP (Extensible Authentication Protocol, RFC 3748): a request-response frame that carries the chosen authentication method, each method with its own type number. The talking is done by the client (supplicant) and the RADIUS server, and the NAS is only an intermediary. 802.1X is the IEEE standard under which a switch port or a Wi-Fi association passes nothing but authentication frames (EAPOL — EAP over LAN) until authentication is complete. The access point repacks EAP frames from EAPOL into EAP-Message attributes and back without understanding the content. The conversation takes a dozen Access-Request / Access-Challenge exchanges: TLS messages don't fit into one packet and are cut into fragments sized by the Framed-MTU attribute the NAS sends (1002 bytes for the access points in the examples below). At the end, if all is well, the server sends an Access-Accept with the MS-MPPE-Recv-Key and MS-MPPE-Send-Key attributes — the material from which the NAS and the client derive the encryption keys for this session (in Wi-Fi this is the PMK that starts the four-way handshake from part 3 of the networking series). The user's password never leaves the TLS tunnel between the client and the RADIUS server (part 23 of the networking series), and the NAS never sees it.

 client              access point (NAS)              NPS                 controller
   │                         │                         │                         │
   ├─ EAPOL: EAP-Response ──►├─ Access-Request ───────►│                         │
   │◄─ EAPOL: EAP-Request ───┤◄─ Access-Challenge ─────┤                         │
   │    … a dozen exchanges: PEAP TLS tunnel, EAP-MSCHAPv2 inside …              │
   │                         │                         ├─ check the response ───►│
   │                         │                         │◄─ yes / no ─────────────┤
   │◄─ EAP-Success ──────────┤◄─ Access-Accept ────────┤                         │
   │                         │   MS-MPPE-*-Key, VLAN   │                         │

Accounting is separate Accounting-Request packets (code 4) with Start, Interim-Update, Stop and byte and time counters; the server replies with Accounting-Response (code 5) and writes to its log.


NPS

The role installs on a server with the graphical shell: the Server Core option has no NPAS role. Traditionally NPS lives on a domain controller because it checks credentials against the directory; for the series it is a separate member server, nps1.corp.example.com:

PS C:\> Install-WindowsFeature NPAS -IncludeManagementTools
PS C:\> Get-Service IAS | Select-Object Status, StartType

 Status StartType
 ------ ---------
Running Automatic

The service is called IAS — Internet Authentication Service, the role's name before 2008; it runs inside svchost as LocalSystem with a delayed start, so after a reboot RADIUS starts answering a minute or two after the system comes up.

NPS has to read the remote access properties of accounts (the Dial-in tab), and that right belongs to the RAS and IAS Servers group (domain local, RID 553). Registering NPS in the domain means adding the computer account to this group:

PS C:\> netsh nps add registeredserver
PS C:\> Get-ADGroupMember 'RAS and IAS Servers' | Select-Object Name

Name
----
NPS1

The documentation requires registration without exceptions. The state is shown by netsh nps show registeredserver; for NPS on a domain controller that hasn't been added to the group, it answers like this — and that server still lets users in, but its policies don't read the Dial-in properties anyway (the "ignore" flag, see below):

PS C:\> netsh nps show registeredserver

Server registration:
---------------------------------------------------------
Status = Un-registered

Ok.

The NPS module is small — RADIUS clients, export and import:

PS C:\> Get-Command -Module NPS | Select-Object -ExpandProperty Name
Export-NpsConfiguration
Get-NpsRadiusClient
Get-NpsSharedSecretTemplate
Import-NpsConfiguration
New-NpsRadiusClient
Remove-NpsRadiusClient
Set-NpsRadiusClient

There are no cmdlets for policies, remote server groups or creating secret templates: that's the console or netsh nps. The module manifest doesn't list Core, so in PowerShell 7 it loads through the compatibility layer — from Windows PowerShell 5.1 in a background session.


Clients

Every device that will send requests is registered with an address and a secret; a request from an unknown address is dropped by NPS, which writes event 13 to the System log:

PS C:\> New-NpsRadiusClient -Name 'ap-floor1' -Address 192.168.1.5 -SharedSecret (Read-Host 'Secret') -AuthAttributeRequired $true
PS C:\> New-NpsRadiusClient -Name 'sw-core' -Address 192.168.1.2 -SharedSecret (Read-Host 'Secret') -VendorName 'Cisco' -AuthAttributeRequired $true
PS C:\> New-NpsRadiusClient -Name 'vpn-gw' -Address 192.168.1.1 -SharedSecret (Read-Host 'Secret')
PS C:\> Get-NpsRadiusClient | Format-Table Name, Address, Enabled, VendorName, AuthAttributeRequired -AutoSize

Name      Address     Enabled VendorName      AuthAttributeRequired
----      -------     ------- ----------      ---------------------
ap-floor1 192.168.1.5    True RADIUS Standard                 False
ap-floor2 192.168.1.6    True RADIUS Standard                 False
ap-floor3 192.168.1.7    True RADIUS Standard                 False
vpn-gw    192.168.1.1    True RADIUS Standard                 False

The output comes from a server configured in the console: every client with the standard dictionary and without AuthAttributeRequired. That is the default, and it is exactly what Microsoft's Blast-RADIUS article tells you to fix: AuthAttributeRequired (requireauthattrib in netsh) makes NPS drop this client's requests without Message-Authenticator (event 17), while without it NPS checks the attribute only when it is present. Before turning it on, make sure the NAS sends it: access points using EAP always do, a switch using PAP for administrator logins not necessarily.

Two warnings about secrets. -SharedSecret is a plain string, and Get-NpsRadiusClient without Select-Object/Format-Table prints every client's secret in clear text, as does Get-NpsSharedSecretTemplate; that must not go into shared logs or onto screens. Second: shared secret templates (one secret for a group of similar devices, so that it changes in one place) are created only in the console; the module can read them but can neither create them nor assign one to a client:

PS C:\> Get-NpsSharedSecretTemplate | Select-Object Name

Name
----
default secret
wifi
vpn

VendorName is for policy conditions by vendor (the list is in netsh nps show vendors). The address can be a subnet (192.168.1.0/24) for a group of devices with one secret — convenient and unsafe: any machine in the subnet becomes a RADIUS client. The secret is all that distinguishes the real NAS from any other device with the same address: long, random, different for different groups of devices.


Policies

A request goes through two queues of policies, each an ordered list where the first match wins.

Connection request policies decide where to authenticate: here, or forward to another RADIUS server (proxy). Conditions are the NAS type, the client address, Called-Station-Id, time, the user name with its realm. The default policy "Use Windows authentication for all users", with the condition "any day, any time", catches everything and authenticates locally (Auth-Provider-Type = 1); it is enough until you need a proxy. A request that matches no connection request policy is rejected. This is also where the find-and-replace rules for User-Name, Called-Station-Id and Calling-Station-Id live: for example, cutting @example.com off the name before the directory lookup.

Network policies decide whether to let in and with what. Each has four parts:

Part What
Conditions what has to match for the policy to apply: Windows groups (of the user or the computer), the NAS port type (NAS-Port-Type: 19 — Wireless-802.11, 15 — Ethernet, 5 — Virtual for VPN), Called-Station-Id (a regular expression over the SSID), Calling-Station-Id, the RADIUS client address and name, day and time, tunnel type
Permission Grant or Deny, and the "ignore user account dial-in properties" flag
Constraints authentication methods (EAP types, MS-CHAPv2, PAP…), timeouts, port type, time; a constraint mismatch is a rejection, not a move to the next policy
Settings what to return in the Access-Accept: standard and vendor RADIUS attributes — VLAN (Tunnel-Type = VLAN, Tunnel-Medium-Type = 802, Tunnel-Pvt-Group-ID = 20, RFC 2868 and 3580), Filter-Id, Session-Timeout, for Cisco shell:priv-lvl=15

The order for each request: the first policy's conditions match → check the constraints (method, port type, time) → check the credentials → check the account restrictions (disabled, expired, logon hours, the "Network Access Permission" property on the Dial-in tab unless the policy says to ignore it) → send Accept with the settings or Reject. Conditions don't match — the next policy. None matches — Reject with code 48, "no matching policy".

This is what the policies the NPS wizard creates for Wi-Fi and VPN look like when read through netsh:

PS C:\> netsh nps show np
Network policy configuration:
---------------------------------------------------------
Name             = Connections to other access servers
State            = Enabled
Processing order = 3
Policy source    = 0

Condition attributes:

Name                                    Id          Value
---------------------------------------------------------
Condition0                              0x1006      "0 00:00-24:00; 1 00:00-24:00; 2 00:00-24:00; 3 00:00-24:00; 4 00:00-24:00; 5 00:00-24:00; 6 00:00-24:00"

Profile attributes:

Name                                    Id          Value
---------------------------------------------------------
NP-Allow-Dial-in                        0x100f      "FALSE"
NP-Authentication-Type                  0x1009      "0x3" "0x4" "0x9" "0xa"
Framed-Protocol                         0x7         "0x1"
Service-Type                            0x6         "0x2"

Network policy configuration:
---------------------------------------------------------
Name             = Virtual Private Network (VPN) Connections
State            = Enabled
Processing order = 2
Policy source    = 0

Condition attributes:

Name                                    Id          Value
---------------------------------------------------------
Condition0                              0x3d        "^5$"
Condition1                              0x1023      "S-1-5-21-1004336348-1177238915-682003330-513"

Profile attributes:

Name                                    Id          Value
---------------------------------------------------------
Ignore-User-Dialin-Properties           0x1005      "TRUE"
NP-Allow-Dial-in                        0x100f      "TRUE"
NP-Allowed-EAP-Type                     0x100a      "1A000000000000000000000000000000"
NP-Authentication-Type                  0x1009      "0x5"
Framed-Protocol                         0x7         "0x1"
Service-Type                            0x6         "0x2"
MS-Link-Utilization-Threshold           0xffffffaa  "0x32"
MS-Link-Drop-Time-Limit                 0xffffffa9  "0x78"
MS-MPPE-Encryption-Policy               0xffffffa7  "0x2"
MS-MPPE-Encryption-Types                0xffffffa6  "0x4"

Network policy configuration:
---------------------------------------------------------
Name             = Secure Wireless Connections
State            = Enabled
Processing order = 1
Policy source    = 0

Condition attributes:

Name                                    Id          Value
---------------------------------------------------------
Condition0                              0x3d        "^18$|^19$"
Condition1                              0x1023      "S-1-5-21-1004336348-1177238915-682003330-513"

Profile attributes:

Name                                    Id          Value
---------------------------------------------------------
EAP-Configuration                       0x1fa2      "19000000000000000000000000000000380000000200000038000000010000001400000043F1B8A07C2D94E6A5B0C7D18E9F2A3B4C5D6E7F0100000001000000100000001A00000000000000"
Ignore-User-Dialin-Properties           0x1005      "TRUE"
NP-Allow-Dial-in                        0x100f      "TRUE"
NP-Allowed-EAP-Type                     0x100a      "1A000000000000000000000000000000" "19000000000000000000000000000000"
NP-Authentication-Type                  0x1009      "0x5" "0x3" "0x9" "0x4" "0xa"
Framed-Protocol                         0x7         "0x1"
Service-Type                            0x6         "0x2"

Ok.

How to read it. Conditions and the profile are stored as attributes with numeric identifiers (the full lists are netsh nps show npconditionattributes and show npprofileattributes). 0x3d is NAS-Port-Type as a regular expression: 18 and 19 are the wireless types, 5 is Virtual. 0x1023 is Windows-Groups, and the value is the group's SID, not its name; here it is RID 513, Domain Users, so this is a policy for users, and a computer authenticating with its own account (it is in Domain Computers, RID 515) won't match it. Constraints and settings sit together in the profile: NP-Allow-Dial-in is the permission (Grant/Deny), 0x1005 ignores Dial-in, NP-Authentication-Type lists the allowed methods (1 — PAP, 3 — MS-CHAP, 4 — MS-CHAPv2, 5 — EAP, 9 and 0xa — MS-CHAP and MS-CHAPv2 with password change), NP-Allowed-EAP-Type lists EAP types: the first byte is the type number, 0x19 = 25 — PEAP, 0x1A = 26 — EAP-MSCHAPv2. EAP-Configuration holds the PEAP settings, and among its bytes you can see the 20-byte thumbprint of the server certificate (43F1B8A0…). Last comes the default deny policy "Connections to other access servers" with the condition "any time": a request that matched none of the working policies gets a rejection from it with code 65, not 48.

From the command line such a policy is created by one netsh nps add np command with conditionid/conditiondata and profileid/profiledata pairs. An example — administrator logins to switches over RADIUS: the switch asks for a password, sends it as PAP inside RADIUS and gets back a privilege level:

PS C:\> $sid = (Get-ADGroup 'Network Admins').SID.Value
PS C:\> netsh nps add np name="Switch admins" state="enable" processingorder="1" policysource="0" conditionid="0x1023" conditiondata="$sid" conditionid="0x100c" conditiondata="192.168.1.2" profileid="0x100f" profiledata="TRUE" profileid="0x1005" profiledata="TRUE" profileid="0x1009" profiledata="0x1" profileid="0x6" profiledata="0x6" profileid="0x1388" profiledata="shell:priv-lvl=15"

0x100c is Client-IP-Address, the RADIUS client's address; 0x6 with value 6 is Service-Type = Administrative-User; 0x1388 is Cisco-AV-Pair, a Cisco attribute inside Vendor-Specific. PAP (Password Authentication Protocol, RFC 1334) is the password as is; in RADIUS it is protected only by the secret, so such a client must have requireauthattrib. The practical way for complex policies is to build the first one in the console and keep the configuration as a file from then on:

PS C:\> Export-NpsConfiguration -Path C:\NPS\nps-config.xml

The file contains the secrets of clients and remote servers in clear text — store it like a password. It is also the way to keep two NPS servers identical: Import-NpsConfiguration -Path on the second one, with the NAS devices pointed at both with the same secret. SQL Server logging settings aren't exported, and the certificate thumbprint in EAP-Configuration points to the first server's certificate — on the second server it has to be selected again.


EAP and the server certificate

For Wi-Fi and wired 802.1X the "authentication methods" constraint in a policy is the EAP type, and the choice between them determines what the client proves:

Method The client proves The server proves Where it is used
PEAP with EAP-MSCHAPv2 domain name and password inside a TLS tunnel server certificate Wi-Fi and 802.1X with passwords: Windows, macOS, iOS, Android
EAP-TLS a client certificate (computer or user) server certificate Wi-Fi and 802.1X without passwords: needs a certificate issued to the client, from part 25
PEAP with EAP-TLS a client certificate inside the tunnel server certificate the same, with the identity protected
EAP-MSCHAPv2 without PEAP name and password by challenge-response the answer to the client's challenge IKEv2 VPN, where IPsec already provides the tunnel (part 28)
MS-CHAPv2 without EAP name and password by challenge-response the answer to the client's challenge old VPNs and equipment without EAP
PAP the password in clear text inside RADIUS nothing administrator logins to equipment that has nothing else

PEAP (Protected EAP, Microsoft's MS-PEAP specification, type 25) is first a TLS tunnel in which the server presents its certificate, then a second, "inner" EAP method inside it. EAP-TLS (RFC 5216, type 13) is mutual TLS authentication: both sides present certificates. MS-CHAPv2 (RFC 2759) is challenge-response: the server sends a random challenge, the client answers with a value computed from the challenge and the NT hash of the password, and checks the server's answer in turn; EAP-MSCHAPv2 (type 26) is the same exchange wrapped in EAP. What NPS doesn't have: EAP-TTLS (RFC 5281, a TLS tunnel with any inner method, even PAP) and TEAP (a tunnel that checks the computer and the user in one session) are supported by Windows only as a client — for other vendors' RADIUS servers.

Two consequences. First: in all tunnel methods the server presents a certificate, and the client must check it — otherwise anyone with an access point broadcasting the same SSID collects users' MSCHAPv2 responses (the "evil twin" attack). The certificate has the Server Authentication purpose (OID 1.3.6.1.5.5.7.3.1), a non-empty subject name and the server's DNS name in the SAN, and comes from a CA the clients trust. AD CS has the RAS and IAS Server template for this (RASAndIASServer): Server and Client Authentication purposes, subject and DNS name from the directory, the Enroll right granted to the RAS and IAS Servers group. The group has no Autoenroll right by default, and the template isn't published on the CA by default: either publish it and grant Autoenroll, or request one by hand — Get-Certificate -Template RASAndIASServer -CertStoreLocation Cert:\LocalMachine\My. A certificate from your own CA suits domain machines, which trust the root through Group Policy; phones and guest devices don't trust it, so for them a certificate from a public CA is sometimes used — and then checking the server name in the client profile is mandatory, because anyone can buy a certificate from the same public CA. The clients get a profile naming the trusted root and the server name; how to distribute it is the next part.

Second: MS-CHAPv2 is essentially NTLM. NTLM is a challenge-response scheme over the NT hash of the password (part 3): NPS doesn't know the password itself and passes the challenge and the response to a domain controller over Netlogon, like any server that verifies NTLM. Accounts in the Protected Users group (part 15) can't authenticate with NTLM, so they can't sign in through PEAP-MSCHAPv2, and EAP-TLS is what remains for them.

Which certificate the server presents is set in the EAP type properties in the policy (in the console) and is stored in the policy as a thumbprint — the very one visible in EAP-Configuration. NPS offers only certificates from the computer store that have the Server Authentication purpose and a non-empty subject; after the certificate is renewed the thumbprint changes, and the new one has to be selected in the policy.

What NPS checks credentials against: for a computer, the CORP\WS1$ account (the client calls itself host/ws1.corp.example.com), for a user, their name; with EAP-TLS, the certificate is mapped to an account: for a user by the UPN in the SAN, for a computer by its DNS name. Since the May 2022 updates an enterprise CA also adds an extension with the owner's SID to its certificates (OID 1.3.6.1.4.1.311.25.2), which domain controllers use to check that the certificate was issued to exactly this account. When you need both — "computer authentication at boot, user authentication at sign-in" — there will be two entries in the policies, by a computer group and by a user group.


Proxy

NPS can forward requests to another RADIUS server instead of answering itself: to the partner from part 22, to a cloud authentication provider, to an old server during a migration. For EAP-TLS and PEAP-TLS between forests it is the only way: according to the documentation, a forest trust doesn't help here, and you need a proxy to the NPS of the other forest. There are no cmdlets for remote server groups, it is all netsh nps: a group, a server in it, and a connection request policy that sends requests to the group by the realm in the name:

PS C:\> netsh nps add remoteservergroup name="Partner RADIUS"
PS C:\> netsh nps add remoteserver remoteservergroup="Partner RADIUS" address="radius.partner.example.net" authport="1812" acctport="1813" authsharedsecret="$(Read-Host 'Secret')" acctsharedsecret="$(Read-Host 'Secret')" requireauthattrib="yes" priority="1" weight="50"
PS C:\> netsh nps add crp name="Forward partner realm" state="enable" processingorder="1" policysource="0" conditionid="0x1" conditiondata=".*@partner\.example\.net$" profileid="0x1025" profiledata="0x2" profileid="0x1029" profiledata="Partner RADIUS"

0x1 in a connection request policy is User-Name, 0x1025 is Auth-Provider-Type (1 — authenticate here, 2 — forward), 0x1029 is the group name. Requests with names *@partner.example.net go to the partner, and the answers return to the NAS as if they were NPS's own. A proxy is useful inside an organization too: one address for all devices with several servers behind it: the group uses the server with priority 1 first, and among servers of the same priority requests are split by weight.

Two flags from the July 2024 updates are also about proxies. requireMsgAuth drops responses from remote servers without Message-Authenticator; limitProxyState drops requests that carry a Proxy-State attribute (an intermediate proxy adds it) but no Message-Authenticator. Both are off by default, each has an audit mode and exceptions for individual servers and clients:

PS C:\> netsh nps show requiremsgauth

Require Message Auth       = disable
Ok.
PS C:\> netsh nps show limitproxystate

Limit Proxy State           = disable
Ok.

They are turned on with netsh nps set requiremsgauth all = "enable" and netsh nps set limitproxystate all = "enable" (audit only writes events 4419 and 4420). While they are off, NPS reminds you about them with event 4421 in the System log every time the service starts.


Logs

Three places. The Security log is the main one: events are written under the Network Policy Server audit subcategory, which on servers is on by default for success and failure — you can check it like this:

PS C:\> auditpol /get /subcategory:"Network Policy Server"
System audit policy

Category/Subcategory                      Setting
Logon/Logoff
  Network Policy Server                   Success and Failure
Event What
6272 access granted: user, RADIUS client, policies, method
6273 access denied, with a reason code and text
6274 request discarded
6275 accounting request discarded
6279, 6280 account locked and unlocked by NPS's remote access lockout

Events 6276–6278 come from NAP (Network Access Protection, a client health check before admission), which is gone since Windows Server 2016.

The System log, source NPS — the state of the service itself and everything related to RADIUS clients:

Event What
13 a message from an address that isn't registered as a client
14 an invalid Authenticator — usually a mismatched secret
17 the client must send Message-Authenticator, but the request came without it
18 an invalid Message-Authenticator — the secret again
19 a request with EAP but without Message-Authenticator
4400, 4402 LDAP connection to a domain controller established; no domain controller available
4418–4420 dropped, or let through in audit mode, under limitProxyState and requireMsgAuth
4421 requireMsgAuth and/or limitProxyState are off or in audit mode

The 6273 reason codes (also in the Reason-Code field of the accounting file) you meet most often: 16 — wrong credentials (the password, a computer with a stale account password, a name in the wrong format); 22 — no EAP type could be negotiated (the client profile doesn't match the policy); 48 — no network policy matched; 49 — no connection request policy matched; 65 — access denied by the permission: Deny in the account's Dial-in properties or in the matching policy itself, as with "Connections to other access servers"; 66 — the authentication method isn't allowed by the matching policy; 8 — the account doesn't exist; 34, 35, 36, 37 — the account is disabled, expired, locked out, outside its logon hours; 265 — the client certificate was issued by a CA that NPS doesn't trust; 258 and 259 — NPS can't check the client certificate's revocation (the CRL is unreachable); 268 — the client certificate has expired. If instead it is the client that doesn't trust the server certificate, the client itself breaks off the EAP conversation, and the cause has to be looked for on the client: NPS sees only an unfinished exchange.

Accounting files — C:\Windows\System32\LogFiles\IN*.log, one file per month (IN2610.log is October 2026). The console offers three formats: DTS Compliant — XML, the same that NPS sends to SQL Server — and two legacy ones, ODBC and IAS, comma-separated lines. Each event is one <Event>…</Event> line with Packet-Type, User-Name, Called-Station-Id, Calling-Station-Id, Client-Friendly-Name, NP-Policy-Name, Authentication-Type, EAP-Friendly-Name, Reason-Code and other elements. The settings can be read through netsh:

PS C:\> netsh nps show filelog

File log configuration:
---------------------------------------------------------
Accounting                     = Enabled
Authentication                 = Enabled
Periodic accounting status     = Enabled
Periodic authentication status = Enabled
Directory                      = C:\Windows\system32\LogFiles
Format                         = ODBC formatting
Delete old logs                = Enabled
Frequency                      = Monthly logs
Max size                       = 10 MB

Ok.

netsh knows only two values here, IAS and ODBC, and shows the DTS Compliant format as ODBC formatting, although the file is XML. The documentation warns about an important property of the file log: if writing fails (the disk is full, for example), NPS stops processing requests, so the log is kept on a separate volume. Logging can also go to SQL Server, local or remote.


Looking with your own eyes

The accounting file is good because it has every packet: let's group a month of it by packet type, policy and reason code.

PS C:\> Invoke-Command -ComputerName nps1 -ScriptBlock {
    $type = @{ '1'='Access-Request'; '2'='Access-Accept'; '3'='Access-Reject'; '4'='Accounting-Request'; '11'='Access-Challenge' }
    $log = Get-ChildItem C:\Windows\System32\LogFiles\IN*.log | Sort-Object Name | Select-Object -Last 1
    Get-Content $log.FullName | ForEach-Object { $e = ([xml]$_).Event
        [PSCustomObject]@{ Packet = $type[$e.'Packet-Type'.'#text']; Policy = $e.'NP-Policy-Name'.'#text'; Reason = $e.'Reason-Code'.'#text' } }
} | Group-Object Packet, Policy, Reason -NoElement | Sort-Object Count -Descending | Select-Object Count, Name

Count Name
----- ----
 5048 Access-Request, Secure Wireless Connections, 0
 4589 Access-Challenge, Secure Wireless Connections, 0
  459 Access-Accept, Secure Wireless Connections, 0
  102 Access-Request, Virtual Private Network (VPN) Connections, 0
   68 Access-Challenge, Virtual Private Network (VPN) Connections, 0
   34 Access-Accept, Virtual Private Network (VPN) Connections, 0
    3 Access-Reject, Connections to other access servers, 65
    3 Access-Request, Connections to other access servers, 0

The numbers add up to the protocol. Wi-Fi over PEAP: 459 connections, 11 Access-Request and 10 Access-Challenge packets each on average. But that is the average of two different conversations. A full connection is 13 Access-Request, 12 Access-Challenge and an Access-Accept, 26 packets: identity, the start of PEAP, the TLS handshake with the server certificate in several fragments, then MSCHAPv2 inside the tunnel and finally an Access-Accept with the keys. A fast reconnect (on in NPS by default) is 5 Access-Request and 4 Access-Challenge: a client that recently passed the full check, for example when moving between access points, resumes the TLS session with NPS from its cache, with no certificate and no MSCHAPv2. The count also includes conversations the client abandoned without waiting for an answer. VPN with EAP-MSCHAPv2 and no tunnel: 34 connections with 2 Access-Challenge and 3 Access-Request each — identity, the MSCHAPv2 challenge-response, success. Three rejections in a month — requests that matched none of the working policies and reached the deny policy, hence code 65. There are no Accounting-Request packets at all: these NAS devices don't send accounting, although the server is ready to log it. One more detail: in the file lines the Authentication-Type field is 11 for PEAP connections and 5 (EAP) for the first packets of a conversation; the documentation's table has no value 11.

Whole packets can be captured on the NPS itself with the built-in pktmon and opened in Wireshark: pktmon filter add -p 1812, pktmon start -c, connect, pktmon stop and pktmon etl2pcap PktMon.etl. The first Access-Request will have User-Name, NAS-IP-Address, NAS-Port-Type = Wireless-802.11, Calling-Station-Id, Called-Station-Id with the SSID, Message-Authenticator and EAP-Message; no packet contains the password — the inner MSCHAPv2 exchange is encrypted by TLS, and the access point forwards it without opening it.

Event 6272 for one of these connections:

PS C:\> Invoke-Command -ComputerName nps1 -ScriptBlock { (Get-WinEvent -FilterHashtable @{LogName='Security'; Id=6272} -MaxEvents 1).Message }
Network Policy Server granted access to a user.

User:
	Security ID:			S-1-5-21-1004336348-1177238915-682003330-1105
	Account Name:			CORP\alice
	Account Domain:			CORP
	Fully Qualified Account Name:	CORP\alice

Client Machine:
	Security ID:			S-1-0-0
	Account Name:			-
	Fully Qualified Account Name:	-
	Called Station Identifier:		AA-BB-CC-DD-EE-FF:CorpWiFi
	Calling Station Identifier:		00-11-22-33-44-55

NAS:
	NAS IPv4 Address:		192.168.1.5
	NAS IPv6 Address:		-
	NAS Identifier:			AABBCCDDEEFF
	NAS Port-Type:			Wireless - IEEE 802.11
	NAS Port:			-

RADIUS Client:
	Client Friendly Name:		ap-floor1
	Client IP Address:			192.168.1.5

Authentication Details:
	Connection Request Policy Name:	Secure Wireless Connections
	Network Policy Name:		Secure Wireless Connections
	Authentication Provider:		Windows
	Authentication Server:		nps1.corp.example.com
	Authentication Type:		PEAP
	EAP Type:			Microsoft: Secured password (EAP-MSCHAP v2)
	Account Session Identifier:		43393444463435364332313530323243
	Logging Results:			Accounting information was written to the local log file.

Who (Account Name is the name as the user typed it), from which device (Calling Station Identifier is the client MAC), through which access point and SSID, by which policies, with which method. The Client Machine section is empty (S-1-0-0 is the null SID): it is filled in when a computer authenticates. This access point fills NAS Identifier with its MAC without separators, and Account Session Identifier is the hexadecimal form of the Acct-Session-Id string (4339… is the characters C9…). The SID in Security ID isn't resolved to a name because the message was rendered in a remote session; in Event Viewer on the server itself you will see the name there.

A summary for the last day, by field numbers from the event template: in 6272 and 6273 the policy is field 17, the authentication type 20, the EAP type 21, and the reason code in 6273 is 23. The values have to be extracted inside Invoke-Command: after deserialization Properties[n].Value is empty.

PS C:\> Invoke-Command -ComputerName nps1 -ScriptBlock {
    Get-WinEvent -FilterHashtable @{LogName='Security'; Id=6272; StartTime=(Get-Date).AddDays(-1)} |
        ForEach-Object { [PSCustomObject]@{ Policy = $_.Properties[17].Value; Auth = $_.Properties[20].Value; Eap = $_.Properties[21].Value } }
} | Group-Object Policy, Auth, Eap -NoElement | Select-Object Count, Name

Count Name
----- ----
   12 Secure Wireless Connections, PEAP, Microsoft: Secured password (EAP-MSCHAP v2)

Twelve, because the Security log is overwritten quickly: the accounting file has 459 Wi-Fi connections for the month, while the Security log of the server this output comes from (NPS runs on a domain controller there, and Kerberos events fill the log as well) holds less than a day. For history, use the accounting file. The same for rejections, grouped by reason and policy — a report worth reading in the morning:

PS C:\> Invoke-Command -ComputerName nps1 -ScriptBlock {
    Get-WinEvent -FilterHashtable @{LogName='Security'; Id=6273; StartTime=(Get-Date).AddDays(-1)} |
        ForEach-Object { [PSCustomObject]@{ Reason = $_.Properties[23].Value; Policy = $_.Properties[17].Value } }
} | Group-Object Reason, Policy -NoElement | Select-Object Count, Name
Get-WinEvent: No events were found that match the specified selection criteria.

There were no rejections in the last day, and Get-WinEvent reports that as an error; in a report script add -ErrorAction SilentlyContinue to it. When there are rejections, the user name is field 3 (DOMAIN\user): forty rejections with code 16 from one service account means a device with an old password, code 22 for one user means the Wi-Fi profile on their machine is set to a different EAP type.

What the server knows about itself — the ports:

PS C:\> netsh nps show ports

Ports configuration:
---------------------------------------------------------
Accounting ports     = 1813,1646
Authentication ports = 1812,1645

Ok.

and the Blast-RADIUS reminder in the System log at every service start:

PS C:\> Invoke-Command -ComputerName nps1 -ScriptBlock { (Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='NPS'; Id=4421} -MaxEvents 1).Message }
RequireMsgAuth and/or limitProxyState configuration is in Disable mode. These settings should be configured in Enable mode for security purposes. See https://support.microsoft.com/help/5040268 to learn more.

And checking that RADIUS answers at all, from a machine with no NAS at hand: UDP can't be checked with Test-NetConnection, but a command-line RADIUS client (radclient from FreeRADIUS on Linux) with a registered address and the secret will send an Access-Request with PAP and show Accept or Reject — and the matching event in the log. Use a separate test account for this: a wrong password in such checks counts as a failed logon and brings the account closer to lockout.


Sources

The RADIUS protocol:

  • RFC 2865 — RADIUS: packets, codes, the Authenticator, hiding User-Password, attributes and port 1812;
  • RFC 2866 — accounting: Accounting-Request and Accounting-Response, Start, Interim-Update, Stop, port 1813;
  • RFC 2869 — RADIUS extensions, the Message-Authenticator attribute (HMAC-MD5);
  • RFC 2868 and RFC 3580 — tunnel attributes and their use with 802.1X: VLANs through Tunnel-Type, Tunnel-Medium-Type, Tunnel-Pvt-Group-ID;
  • RFC 2548 — Microsoft attributes inside Vendor-Specific, MS-MPPE-Send-Key and MS-MPPE-Recv-Key;
  • RFC 6614 — RADIUS over TLS (RadSec);
  • KB5040268 — Blast-RADIUS (CVE-2024-3596) in NPS: requireauthattrib, limitProxyState, requireMsgAuth, defaults and events 4418–4421.

EAP and authentication methods:

Network Policy Server:

Logs:

The directory side:


NPS is ready to answer; the access points are registered as clients; the server certificate is issued. What is left is to teach workstations to ask for permission before they go on the air.

Next — part 27: Wi-Fi with 802.1X. WPA2-Enterprise and WPA3-Enterprise and how they differ from a home password; the supplicant, authenticator and authentication server roles; setting up an access point as a RADIUS client; a Wi-Fi profile for domain machines through Group Policy — with server certificate validation, computer authentication at boot and user authentication at sign-in; NPS policies for Wi-Fi with VLANs; and what it looks like from a phone that isn't in the domain.

Only registered users can leave a comment.

Sign in or register to leave a comment.