# AADSTS50027: JWT token is invalid or malformed (fix)

Canonical: https://brambrinkmeier.com/entra/aadsts/50027/
By Bram Brinkmeier. JSON: https://brambrinkmeier.com/api/aadsts/50027.json

**Short answer:** AADSTS50027 means Microsoft Entra ID received a JWT it would not accept: wrong format, missing or duplicated claims, unexpected issuer or audience, outside its valid time range, or a failed signature check on an external ID token. It is usually a developer error. The two common sources are a hand-built client assertion and a federated identity provider's ID token.

**Fix:** Developer sending a client assertion: build a real JWT with the header and claims Microsoft documents, signed with the certificate registered on the app. Federation admin: fix the issuer, audience or signing key on the identity provider.

| Field | Value |
|---|---|
| Code | AADSTS50027 |
| Microsoft name | InvalidJwtToken |
| Category | Credentials and lockout |
| Type | Failure |
| Attack signal | Medium: worth a look in context |
| Where to look | `SigninLogs`, `AADServicePrincipalSignInLogs` |
| In Microsoft's error reference | Yes |
| Verification | lab-reproduced (2026-10-30) |
| Last updated | 2026-10-30 |

## What does AADSTS50027 mean?

Microsoft's description:

> **InvalidJwtToken**: Invalid JWT token because of the following reasons:
> - doesn't contain nonce claim, sub claim
> - subject identifier mismatch
> - duplicate claim in idToken claims
> - unexpected issuer
> - unexpected audience
> - not within its valid time range
> - token format isn't proper
> - External ID token from issuer failed signature verification.

That is eight different conditions behind one code, which is why the fix depends entirely on which JWT Entra was looking at. Two kinds of request put a JWT in front of Entra:

- **A client assertion.** An app authenticating with a certificate or a federated credential sends `client_assertion` instead of a secret. Our lab hit 50027 this way.
- **An external ID token.** A federated identity provider returns an ID token to Entra, which validates it. The last bullet in Microsoft's list, *"External ID token from issuer failed signature verification"*, is this case, and so are the `nonce`, `sub` and duplicate-claim bullets, which name `idToken` explicitly.

For the client assertion case, Microsoft's certificate credentials page documents what the JWT must contain. The header:

| Parameter | Microsoft's remark |
|---|---|
| `alg` | Should be **PS256** |
| `typ` | Should be **JWT** |
| `x5t#S256` | Base64url-encoded SHA-256 thumbprint of the X.509 certificate's DER encoding. |

And the claims: `aud` (`https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token`), `iss` and `sub` (both the client ID), `jti`, `nbf`, `exp` and `iat`. On the lifetime, Microsoft says to *"keep it short - 5-10 minutes after `nbf` at most"*. The signature *"is computed by applying the certificate as described in the JSON Web Token RFC7519 specification"*, using PSS padding.

Several of Microsoft's eight bullets map onto that table: `aud` wrong is "unexpected audience", `iss` wrong is "unexpected issuer", and an `exp` in the past is "not within its valid time range".

### Which assertion error you get, and in what order
Our lab separated three of the assertion failures. Entra checks them in this order:

| What is wrong | Code |
|---|---|
| `client_assertion_type` is not the JWT bearer URN | [90023](https://brambrinkmeier.com/entra/aadsts/90023/) *"Unexpected client assertion type."* |
| The assertion is not a well-formed JWT | **50027** |
| The assertion is a JWT but its validity window has passed or not begun | [700024](https://brambrinkmeier.com/entra/aadsts/700024/) |
| Nothing was sent at all on a `client_credentials` request | [7000216](https://brambrinkmeier.com/entra/aadsts/7000216/) |

Microsoft's reference also lists **AADSTS700027**, *"Client assertion failed signature validation"*, for a well-formed JWT with a bad signature. So 50027 sits between "wrong kind of parameter" and "right shape, wrong signature": the string reached the JWT parser and the parser rejected it.

## Reproduced in the lab

On 30 October 2026 an automated lab run sent a client-credentials token request with the correct `client_assertion_type` and a `client_assertion` that was not a JWT. Entra returned:

> AADSTS50027: JWT token is invalid or malformed.

Both rounds of the run returned the same code and the same one-line message, short enough that the lab's 160-character cut does not truncate it. The run had expected 9002313, 50027 or 700027, and 50027 is the one it got. In the same run, the same request with an unaccepted `client_assertion_type` returned [90023](https://brambrinkmeier.com/entra/aadsts/90023/), and a syntactically valid assertion whose validity window had passed returned [700024](https://brambrinkmeier.com/entra/aadsts/700024/) with the current time and the assertion's `nbf` in the message. So the lab confirms the format case specifically, and not the other seven conditions in Microsoft's list.

No sign-in log row was captured. See [how pages are checked](https://brambrinkmeier.com/methodology/).

## Is it an attack?

Usually not, and this is the one code on this page's family where the answer deserves a second look. A malformed JWT is what a developer produces; it is also what someone replaying or editing a captured token produces.

| Pattern | Likely meaning |
|---|---|
| Repeated 50027 for one app from your own build agents or servers | A client assertion implementation under development, or a certificate rotation that went wrong. Benign. |
| 50027 on an app that has worked for months, with no deployment | Look at the certificate. An expired or replaced certificate usually gives a different code, but a half-finished rotation can produce assertions nobody can parse. |
| 50027 for one app from an address you don't own | Someone is sending assertions for your app ID. They do not have a working credential, or you would see a success instead. Check for successes from the same address. |
| 50027 on user sign-ins through a federated identity provider | The identity provider's ID token is being rejected. Check the issuer, the audience and the signing key on the provider, and whether a certificate was rolled. |
| A burst across several apps from one address | Someone is probing with crafted tokens. Nothing is disclosed by this error, but the source is worth a look. |

Our reading on the signal: 50027 means a token was refused, so it is evidence of a failure rather than of a compromise. What makes it worth watching is a success from the same source shortly after.

## How do I find it in my logs (KQL)?

Which table 50027 lands in depends on which JWT failed, and Microsoft doesn't document it for either case. Our reading: a client assertion is an app signing in, so `AADServicePrincipalSignInLogs`; a federated user's ID token is a user sign-in, so `SigninLogs` or `AADNonInteractiveUserSignInLogs`. Start with the union:

```kusto
union withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs
| where TimeGenerated > ago(30d)
| where ResultType == "50027" or ResultSignature == "50027"
| summarize Rows = count(),
            FirstSeen = min(TimeGenerated),
            LastSeen = max(TimeGenerated),
            Apps = make_set(AppId, 10)
          by SourceTable
```

The filter covers both columns because the tables describe them differently: in `SigninLogs`, `ResultType` *"provides the 5-6 digit error code that's generated during a sign-in event"*, while `AADServicePrincipalSignInLogs` describes `ResultType` as *"Success or Failure"* and `ResultSignature` as *"the error code, if any, for the sign-in operation"*.

For the client assertion case, which app and which source:

```kusto
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(7d)
| where ResultType == "50027" or ResultSignature == "50027"
| summarize Failures = count(),
            LastSeen = max(TimeGenerated),
            CredentialTypes = make_set(ClientCredentialType, 5),
            Thumbprints = make_set(ServicePrincipalCredentialThumbprint, 5),
            Descriptions = make_set(ResultDescription, 5)
          by AppId, ServicePrincipalName, IPAddress
| sort by Failures desc
```

`ClientCredentialType` is documented as *"the type of client credential used. Examples include client assertion, client secret, etc."* Whether it and the thumbprint column are filled in when the JWT could not be parsed is not documented: a token Entra cannot read may not identify a credential at all. Check a row before you rely on either column.

The pattern worth alerting on is a refusal followed by a success from the same place:

```kusto
let failedFrom =
    AADServicePrincipalSignInLogs
    | where TimeGenerated > ago(7d)
    | where ResultType == "50027" or ResultSignature == "50027"
    | summarize FirstFail = min(TimeGenerated) by AppId, IPAddress;
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(7d)
| where ResultType == "0" or ResultType =~ "Success"
| join kind=inner failedFrom on AppId, IPAddress
| where TimeGenerated > FirstFail
| project TimeGenerated, AppId, ServicePrincipalName, IPAddress, ClientCredentialType, FirstFail
| sort by TimeGenerated asc
```

Thresholds are a starting point only: for a developer's own address, dozens an hour mean nothing; for an address with no history for that app, one row is enough to look at.

## How does Conditional Access affect it?

Not relevant to the client assertion case: there is no user and no policy evaluation, and the request fails on the credential. For the federated user case, 50027 happens while Entra is validating the incoming token, which is before the point where Conditional Access decides anything. Our reading, by analogy with other first-factor failures; Microsoft doesn't state it for this code.

## How do I fix AADSTS50027?

**For developers sending a client assertion:**
1. Decode what you are sending. If it isn't three base64url segments separated by dots, it is not a JWT and nothing else matters.
2. Build the header with `alg` PS256, `typ` JWT and `x5t#S256` set to the certificate's SHA-256 thumbprint, and the claims `aud`, `iss`, `sub`, `jti`, `nbf`, `exp` and `iat` as Microsoft's certificate credentials page specifies. `iss` and `sub` are both the client ID.
3. Keep the lifetime short, 5 to 10 minutes after `nbf`. If the window has passed you get [700024](https://brambrinkmeier.com/entra/aadsts/700024/) rather than 50027.
4. Sign with the private key of a certificate registered on the app registration, using PSS padding.
5. Let a library do it. MSAL builds the assertion from a certificate for you, and most 50027 in this case comes from hand-rolled code.
6. Send it with `client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer`, or you get [90023](https://brambrinkmeier.com/entra/aadsts/90023/) first.

**For admins of a federated identity provider:**
1. Work out which of Microsoft's eight conditions applies before changing anything. The `nonce`, `sub`, duplicate-claim and signature-verification bullets are all about an external ID token.
2. Check the issuer and audience values configured on both sides, and that the provider's current signing key is the one Entra is reading. A key rollover on the provider gives *"External ID token from issuer failed signature verification"*.
3. Check clock skew. *"Not within its valid time range"* is a time problem on the provider, not in Entra.

**For admins generally:**
1. Use the queries above to identify the app and its owner, and whether any source that failed also succeeded.
2. If an address with no history for the app is sending assertions, treat the app ID as known to someone outside and review the app's permissions.

## Evidence

Reproduced in a lab tenant on 2026-10-30 (Client-credentials token request against a lab tenant; scenario: Client assertion that is not a JWT). First line returned:

> AADSTS50027: JWT token is invalid or malformed.

Lab-run rows naming this code (7; full list at https://brambrinkmeier.com/lab-runs.json):

| Date | Scenario | Expected | Got | Message (first line) |
|---|---|---|---|---|
| 2026-10-30 | Client assertion that is not a JWT | 9002313 / 50027 / 700027 | 50027 | AADSTS50027: JWT token is invalid or malformed. |
| 2026-10-30 | Client assertion that is not a JWT | 9002313 / 50027 / 700027 | 50027 | AADSTS50027: JWT token is invalid or malformed. |
| 2026-10-30 | Client assertion that is not a JWT | 9002313 / 50027 / 700027 | 50027 | AADSTS50027: JWT token is invalid or malformed. |
| 2026-10-30 | Client assertion that is not a JWT | 9002313 / 50027 / 700027 | 50027 | AADSTS50027: JWT token is invalid or malformed. |
| 2026-10-30 | Client assertion that is not a JWT | 9002313 / 50027 / 700027 | 50027 | AADSTS50027: JWT token is invalid or malformed. |
| 2026-10-31 | Client assertion that is not a JWT | 9002313 / 50027 / 700027 | 50027 | AADSTS50027: JWT token is invalid or malformed. |
| 2026-10-31 | Client assertion that is not a JWT | 9002313 / 50027 / 700027 | 50027 | AADSTS50027: JWT token is invalid or malformed. |

## Sources

1. [Microsoft Entra authentication and authorization error codes](https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes)
2. [Microsoft identity platform application authentication certificate credentials](https://learn.microsoft.com/en-us/entra/identity-platform/certificate-credentials)
3. [Microsoft identity platform and the OAuth 2.0 client credentials flow](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow)
4. [AADServicePrincipalSignInLogs table reference (Azure Monitor)](https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/aadserviceprincipalsigninlogs)
5. [SigninLogs table reference (Azure Monitor)](https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/signinlogs)

## Related codes

[AADSTS90023](https://brambrinkmeier.com/entra/aadsts/90023/), [AADSTS700024](https://brambrinkmeier.com/entra/aadsts/700024/), [AADSTS7000216](https://brambrinkmeier.com/entra/aadsts/7000216/), [AADSTS7000218](https://brambrinkmeier.com/entra/aadsts/7000218/)

## License and citation

CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). CC BY 4.0 covers the author's own text and classification fields. Quoted Microsoft error wording belongs to Microsoft, is quoted with its source and can change.

Cite as: Bram Brinkmeier, "AADSTS50027: JWT token is invalid or malformed (fix)", https://brambrinkmeier.com/entra/aadsts/50027/ (CC BY 4.0), last updated 2026-10-30.
