Azure Virtual Desktop RDP returns SEC_E_INVALID_TOKEN when validating JWS token with Entra ID PoP authentication
11:36 04 Feb 2026

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

  1. ✅ Generate 2048-bit RSA PoP key pair

  2. ✅ Acquire PoP access token from Entra ID (using req_cnf and token_type=pop parameters)

  3. ✅ Establish TLS connection with RDP server

  4. ✅ Receive server nonce (ts_nonce)

  5. ✅ Create and sign JWS token

  6. ❌ 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!

azure oauth-2.0 rdp microsoft-entra-id