JWT Guides

How I Handle JWT alg none Without Accidentally Trusting It

See how I inspect JWT alg none safely, why unsecured JWTs have no signature integrity, and how I use algorithm allowlists instead of trusting the token header.

By JWTDecoder.toolsLast verified 2026-09-12

Editorial voice: first-person phrasing describes the documented JWTDecoder.tools workflow; JWTDecoder.tools is the publisher and organizational author.

alg=none ∉ application allowlist
token alg → application allowlist → accept / reject

When I decode a JWT and see:

{
  "alg": "none"
}

I stop treating it like an ordinary signed token. alg: none means the JWS is unsecured: there is no cryptographic signature or MAC protecting the header and payload.

That does not mean the format is invalid in every possible context. It does mean I never let the token decide for itself that my application should accept an unsigned JWT.

My first question is:

Did the application explicitly define this exact token context as one where unsecured JWTs are allowed?

If the answer is no, I reject it.

Step 1: I recognize what alg: none actually means

RFC 7518 defines none as the Unsecured JWS algorithm. RFC 7519 also gives an example of an Unsecured JWT whose signature segment is empty.

The compact form still looks JWT-like:

header.payload.

There are still three dot-separated segments, but the signature value is empty.

I can decode the header and payload with the JWT Decoder, but I label the result as readable data only. There is no signature to verify.

Step 2: I do not use the JWT header as my security policy

The dangerous implementation pattern is conceptually:

if (header.alg === "none") {
  acceptWithoutSignature();
}

I never design a verifier that way.

The alg header is input from the token. Before I authenticate anything, the token is untrusted data. Letting it choose whether verification is required gives an attacker control over the verification policy.

Instead, my application configuration decides which algorithms are allowed:

const allowedAlgorithms = ["RS256"];

Then I require the incoming token algorithm to be one of those explicitly configured values.

RFC 8725 is very direct about this: libraries must let callers specify a supported algorithm set and must not use other algorithms for cryptographic processing.

Step 3: I understand the historical failure mode

The classic alg: none vulnerability was not that the JWT standards secretly required applications to trust unsigned tokens.

The problem was that some implementations accepted the token-provided algorithm too loosely and treated none as if it were an automatically valid verification mode.

A signed token could conceptually be transformed into an unsecured token by changing the header and removing the signature. A broken verifier might then accept the claims without proving integrity.

That is why I focus on the verifier's allowlist, not on trying to make the decoder "detect malicious tokens" by itself.

Step 4: I separate decoding from verification

A JWT with alg: none can still contain perfectly readable JSON:

{
  "sub": "user_123",
  "role": "admin",
  "exp": 1789203600
}

Those values tell me what the payload claims. They do not tell me who created it.

With no signature or MAC, anyone who can construct the token can choose those claim values.

So I never use this logic:

payload parsed successfully
→ claims look normal
→ accept user identity

If I need authentication, I require the cryptographic protections and validation rules defined by the application.

Step 5: I check whether none is explicitly allowed by the protocol or deployment

RFC 7518 does not say unsecured JWS is universally forbidden. It says unsecured JWS provides no integrity protection and therefore only makes sense where integrity is provided by other means or is not required.

RFC 8725 similarly notes that none can be acceptable in a context where the JWT is cryptographically protected by another mechanism, but libraries should not generate or consume it unless explicitly requested.

That word — explicitly — is how I approach it.

I do not create one global switch like:

allowUnsignedJwt = true

for an application that processes many JWT types.

If an application genuinely has an unsecured-token use case, I isolate it with its own mutually exclusive validation rules.

Step 6: I avoid "support every algorithm" verifier logic

When I build or configure JWT verification, I prefer a narrow allowed set.

For example, if my identity provider signs with RS256:

Allowed: RS256
Rejected: HS256, none, ES256, anything else

unless the deployment intentionally supports additional algorithms.

This reduces algorithm confusion and makes unexpected tokens fail closed.

The JWT Signature Verifier follows that philosophy by supporting a known set of algorithms rather than treating arbitrary alg values as executable instructions.

The JWKS Validator also checks whether the token algorithm is compatible with the selected JWK instead of relying on kid alone.

Step 7: I treat none differently from an invalid signature

These are different states.

Invalid signature:

A cryptographic signature is present, but verification failed.

alg none:

The token explicitly says there is no signature algorithm, and the signature segment is empty.

That distinction matters for debugging. An unsecured JWT does not have a "bad" signature — it has no signature integrity at all.

If I see a token unexpectedly switch from RS256 to none, I investigate the token source and verifier policy immediately rather than trying another public key.

Step 8: I check the entire JWT acceptance policy, not only alg

Even a token using a strong allowed algorithm still needs other checks.

I validate things such as:

  • signature verification;
  • trusted issuer;
  • audience;
  • exp and nbf;
  • token type;
  • application-specific scopes or roles.

alg: none is a dramatic example because it removes signature integrity, but algorithm enforcement is only one part of JWT validation.

How I would configure a server-side verifier

The exact API varies by library, but the policy I want looks conceptually like this:

verifyJwt(token, trustedKey, {
  algorithms: ["RS256"],
  issuer: "https://login.example.com/",
  audience: "api://orders"
});

The important detail is that the allowed algorithm comes from trusted application configuration, not from the token.

If alg is none, the verification library should reject it unless the caller has explicitly enabled that exact unsecured mode.

What I do when a production token unexpectedly uses alg: none

My checklist is:

  1. I decode the JWT and confirm the header really contains "alg":"none".
  2. I check whether the signature segment is empty as expected for an unsecured JWS.
  3. I identify which service created the token.
  4. I confirm the expected algorithm from trusted configuration.
  5. I inspect whether the verifier has an algorithm allowlist.
  6. I reject the token if none is not explicitly permitted for that token type.
  7. I check for other token-type confusion or fallback verification paths.
  8. I review logs for similar unexpected algorithm changes.

I do not "fix" the token by editing none back to RS256. Changing the header does not recreate a valid signature.

My rule for JWT alg: none

The rule I follow is simple:

The application chooses whether unsecured JWTs are allowed; the token never gets to make that decision.

If the current authentication flow expects signed JWTs, I reject alg: none.

If a specialized protocol explicitly allows unsecured JWTs because integrity is guaranteed elsewhere, I enable that behavior only in that isolated context and keep its validation rules separate from normal signed-token authentication.

That approach matches the core lesson from RFC 8725: algorithm agility is useful, but only when the application — not untrusted input — controls the allowed algorithms.

Sources

Related guides