I'm implementing Entra ID authentication for RDP connections to Azure Virtual Desktop in Java, following the MS-RDPBCGR specification (Section 5.4.5.4.1). The Entra ID token acquisition succeeds, but the RDP server rejects my JWS token with SEC_E_INVALID_TOKEN (0x80090308).
Authentication Flow
✅ Generate 2048-bit RSA PoP key pair
✅ Acquire PoP access token from Entra ID (using
req_cnfandtoken_type=popparameters)✅ Establish TLS connection with RDP server
✅ Receive server nonce (ts_nonce)
✅ Create and sign JWS token
❌ Server returns:
{"authentication_result":-2146893048}(SEC_E_INVALID_TOKEN)
JWS Token Structure
Header:
{"alg":"RS256","kid":"tJjUA6..."}
Payload:
{
"ts":"1769794995",
"at":"eyJ0eX...", // PoP access token
"u":"ms-device-service://termsrv.wvd.microsoft.com/name/[hostname]",
"nonce":"gTNPdx...",
"cnf":{
"jwk":{
"kty":"RSA",
"e":"AQAB",
"n":"1D9aCP..."
}
},
"client_claims":"{\"aad_nonce\":\"AwABEg...\"}"
}
Signature: RS256 using the PoP private key
JWS Creation Code (Java)
// Create JWS header
String headerJson = String.format("{\"alg\":\"RS256\",\"kid\":\"%s\"}", kid);
String encodedHeader = Base64URL.encode(headerJson.getBytes(StandardCharsets.UTF_8)).toString();
// Create JWS payload
String payloadJson = String.format(
"{\"ts\":\"%d\",\"at\":\"%s\",\"u\":\"%s\",\"nonce\":\"%s\",\"cnf\":{\"jwk\":%s},\"client_claims\":\"%s\"}",
timestamp, accessToken, serviceUri, tsNonce, jwkJson, clientClaimsJson
);
String encodedPayload = Base64URL.encode(payloadJson.getBytes(StandardCharsets.UTF_8)).toString();
// Sign with RSA-SHA256
String signingInput = encodedHeader + "." + encodedPayload;
Signature rsaSigner = Signature.getInstance("SHA256withRSA");
rsaSigner.initSign(privateKey);
rsaSigner.update(signingInput.getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes = rsaSigner.sign();
String signature = Base64URL.encode(signatureBytes).toString();
// Final JWS: header.payload.signature
String jws = encodedHeader + "." + encodedPayload + "." + signature;
What I've Verified
✅ PoP key thumbprint (kid) matches across:
JWS header:
tJjUA6...Access token
cnf.kid:tJjUA6...Computed from JWK using RFC 7638 (lexicographic order:
e,kty,n)
✅ JWK field order in payload matches FreeRDP (natural order: kty, e, n)
✅ Access token is valid (verified at jwt.ms, contains correct cnf claim)
✅ Timestamp format is string (not number), per MS-RDPBCGR
✅ client_claims is escaped string (not JSON object), per FreeRDP reference
Environment
Java 11
Nimbus JOSE JWT 9.x for JWS operations
Entra ID tenant with AVD app registration
Target: Azure Virtual Desktop (Windows 11 Enterprise multi-session)
Question
What could cause the RDP server to reject a JWS token with SEC_E_INVALID_TOKEN when the PoP key thumbprint matches and the token structure follows the MS-RDPBCGR specification?
Are there any subtle encoding issues, field ordering requirements, or signature validation quirks specific to Azure Virtual Desktop's RDP implementation that might not be documented in MS-RDPBCGR?
I've compared my implementation with FreeRDP's source code and believe the structure matches, but the server still rejects it. Any insights would be greatly appreciated!