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-KeyandMS-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:
- RFC 3748 and RFC 3579 — EAP and carrying it in RADIUS, Message-Authenticator mandatory with
EAP-Message; - EAP registry — EAP type numbers: 13, 25, 26;
- [MS-PEAP] — the PEAP specification;
- RFC 5216 — EAP-TLS;
- RFC 2759 — MS-CHAPv2;
- RFC 5281 — EAP-TTLS;
- RFC 1334 — PAP;
- Extensible Authentication Protocol (EAP) for network access in Windows — EAP methods in Windows, EAP-TTLS and TEAP client-side only, checking the server certificate and name.
Network Policy Server:
- Network Policy Server (NPS) overview — server and proxy, no role on Server Core, clients by address range, EAP-TLS between forests through a proxy;
- RADIUS Clients — client properties, the secret, Message-Authenticator and EAP;
- Connection Request Policies — connection request policies, the default policy, attribute manipulation rules, forwarding;
- Network Policies — policy order, conditions, constraints (rejection without evaluating further policies), ignoring Dial-in;
- Use Regular Expressions in NPS — regular expressions in conditions and manipulation rules;
- Remote RADIUS Server Groups — remote server groups, priority and weight;
- Register an NPS in an Active Directory Domain — the RAS and IAS Servers group and
netsh nps add registeredserver; - Configure Certificate Templates for PEAP and EAP requirements — server and client certificate requirements;
- Get-Certificate — requesting a certificate from a template in PowerShell;
- Nps Module, New-NpsRadiusClient and Export-NpsConfiguration — the module's seven cmdlets, client parameters, secrets in clear text in the export;
- Network Policy Server Management with Administration Tools and Netsh Commands for Network Policy Server — the
netsh npscontext.
Logs:
- Configure Network Policy Server Accounting — accounting files, DTS Compliant, ODBC and IAS formats, SQL Server, request processing stops when logging fails;
- Interpret NPS Database Format Log Files — record fields,
Packet-Type,Authentication-Type,Reason-Code, an XML example; - NPS Reason Codes — rejection reason codes;
- Audit Network Policy Server — the audit subcategory and events 6272–6280;
- Pktmon command formatting — packet capture with the built-in
pktmon.
The directory side:
- Protected Users Security Group — NTLM is blocked for group members;
- KB5014754 — the SID extension in certificates and strong certificate mapping on domain controllers.
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.