{"record":{"id":"89cf0db807b48b9e","repo":"microsoft/aspire","slug":"the-access-token-returned-by-the-credential-is-not-a-valid","errorCode":null,"errorMessage":"The access token returned by the credential is not a valid JWT (expected 3 '.'-separated segments, found {parts.Length}).","messagePattern":"The access token returned by the credential is not a valid JWT \\(expected 3 '\\.'-separated segments, found (.+?)\\)\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Azure/Provisioning/Internal/DefaultAzurePrincipalProvider.cs","lineNumber":39,"sourceCode":"    // Values accepted by the `principalType` property on Microsoft.Authorization/roleAssignments.\n    // We don't emit \"Group\" here because access tokens never represent a group identity directly.\n    private const string PrincipalTypeUser = \"User\";\n    private const string PrincipalTypeServicePrincipal = \"ServicePrincipal\";\n\n    public async Task<AzurePrincipal> GetPrincipalAsync(CancellationToken cancellationToken = default)\n    {\n        var credential = tokenCredentialProvider.TokenCredential;\n        var response = await credential.GetTokenAsync(new([\"https://graph.windows.net/.default\"]), cancellationToken).ConfigureAwait(false);\n\n        static AzurePrincipal ParseToken(in AccessToken response)\n        {\n            // A JWT is \"header.payload.signature\". The token credential should always return\n            // that shape, but guard explicitly so a malformed token surfaces as a clear error\n            // instead of a confusing IndexOutOfRangeException deep in the parser.\n            var parts = response.Token.Split('.');\n            if (parts.Length < 3)\n            {\n                throw new InvalidOperationException(\n                    $\"The access token returned by the credential is not a valid JWT (expected 3 '.'-separated segments, found {parts.Length}).\");\n            }\n\n            // Decode the JWT payload (the middle segment). JWTs use base64url with stripped\n            // padding (RFC 7515 §2), so swap the URL-safe characters back and re-pad to a length\n            // divisible by four before base64-decoding. Example payload shape:\n            //   { \"oid\":\"<guid>\",\"upn\":\"user@contoso.com\",\"idtyp\":\"user\",\"iss\":\"...\" }\n            // For app-only (service principal) tokens the `upn` claim is absent and `idtyp` is \"app\".\n            var part = parts[1];\n            var convertedToken = part.Replace('_', '/').Replace('-', '+');\n\n            switch (part.Length % 4)\n            {\n                case 2:\n                    convertedToken += \"==\";\n                    break;\n                case 3:\n                    convertedToken += \"=\";","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Azure/Provisioning/Internal/DefaultAzurePrincipalProvider.cs#L21-L57","documentation":"DefaultAzurePrincipalProvider.GetPrincipalAsync obtains an access token from the configured TokenCredential and decodes it as a JWT to read identity claims (oid, tid, idtyp). A JWT must have three dot-separated segments (header.payload.signature); when the credential returns a token with fewer segments, the provider throws a descriptive InvalidOperationException instead of crashing in the base64 decoder. This indicates the credential returned something that is not a JWT (e.g. an error page or opaque token).","triggerScenarios":"Calling GetPrincipalAsync when TokenCredential.GetTokenAsync returns a string that is not a JWT — for example a credential whose token endpoint returned an HTML error response, a test/fake credential returning arbitrary text, or a misconfigured custom TokenCredential.","commonSituations":"Developers hit this with custom or mocked TokenCredential implementations, proxies/interceptors that mangle auth responses, corporate proxies returning error bodies, or auth endpoints returning non-token content that the credential passes through.","solutions":["Inspect the credential's token source — fix the credential configuration so it returns a real Entra ID access token (log only the token shape/segment count, never the value).","Use standard Azure.Identity credentials (DefaultAzureCredential, ClientSecretCredential) instead of custom implementations.","If behind a corporate proxy, ensure HTTPS auth endpoints are not intercepted/rewritten with error pages.","In tests, make fake credentials return well-formed 'header.payload.signature' JWTs with a valid base64url payload containing an oid claim."],"exampleFix":"// before\npublic class FakeCredential : TokenCredential\n{\n    public override AccessToken GetToken(...) => new(\"not-a-jwt\", DateTimeOffset.UtcNow.AddHours(1));\n}\n// after\npublic class FakeCredential : TokenCredential\n{\n    private const string Payload = \"eyJvaWQiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAifQ\"; // {\"oid\":\"00000000-...\"}\n    public override AccessToken GetToken(...) => new($\"header.{Payload}.signature\", DateTimeOffset.UtcNow.AddHours(1));\n}","handlingStrategy":"validation","validationCode":"var parts = token.Split('.');\nif (parts.Length < 3)\n    throw new InvalidOperationException(\"Credential returned a non-JWT token; check the credential configuration/auth endpoint.\");","typeGuard":"bool LooksLikeJwt(string token) =>\n    !string.IsNullOrWhiteSpace(token) && token.Split('.').Length >= 3;","tryCatchPattern":"try\n{\n    var principal = await principalProvider.GetPrincipalAsync(ct);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"not a valid JWT\"))\n{\n    // replace/repair the credential so it returns a real Entra ID access token\n}","preventionTips":["Use standard Azure.Identity credentials rather than custom TokenCredential implementations.","Never log full tokens; log only segment counts when diagnosing token shape issues.","In tests, have fake credentials mint well-formed header.payload.signature JWTs with base64url payloads.","Watch for corporate proxies rewriting auth endpoint responses into error pages."],"tags":["azure","jwt","token","credential","identity"],"backgroundTag":"invalid-argument-format","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}