Skip to content
pwnsy
web-securityintermediate#jwt#web-security#authentication#api-security#appsec

JWT Attacks: alg=none, Algorithm Confusion & Weak Secrets

How JSON Web Token attacks work: the alg=none trick, RS256-to-HS256 algorithm confusion, brute-forcing weak secrets, and how to defend.

A JSON Web Token is a small, signed piece of state the client carries around and hands back to prove who it is. The design is clean, and when the signature check is done correctly the token is hard to forge. The trouble is that the signature check is easy to do incorrectly, and a whole family of attacks exists purely to make the server accept a token it should have rejected.

This guide walks through the main JWT attacks you will actually encounter, why each one works, and the small set of server-side rules that shut them all down. If you want the mechanics of the token format itself first, start with What Is a JWT.

A quick refresher on structure

A JWT has three parts, separated by dots: a header, a payload, and a signature, each Base64url-encoded.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGljZSIsInJvbGUiOiJ1c2VyIn0.<signature>

The header names the signing algorithm, for example HS256 (HMAC with a shared secret) or RS256 (RSA with a private and public key pair). The payload carries claims such as the subject, the role, and an expiry. The signature is computed over the header and payload using the chosen algorithm and key.

The security model rests on one thing: only the server holds the key needed to produce a valid signature, so only the server can issue a token, and anyone can verify one without being able to alter it. Every attack below is an attempt to break that single assumption.

The alg=none attack

The JWT specification allows an alg value of none, meaning the token is unsigned. It exists for cases where the transport is already trusted. In practice it is a loaded gun pointed at any library that honors it by default.

The attack is straightforward. The attacker takes a valid token, edits the payload to elevate their role or change the subject, sets the header algorithm to none, and deletes the signature. A server that reads the algorithm from the token header and finds none may conclude that no signature is required, and accept the forged claims as genuine.

{"alg":"none","typ":"JWT"}.{"sub":"alice","role":"admin"}.

No key, no cryptography, no effort. The root cause is that the server trusted the token to tell it how to verify the token.

The token never decides its own algorithm

The header of a JWT is attacker-controlled. If your verification logic reads alg from the incoming token and acts on it, the attacker chooses the verification path. Decide the accepted algorithm on the server, from configuration, and reject anything that does not match, including none.

Algorithm confusion: RS256 to HS256

This one is more subtle and more dangerous. It abuses the difference between asymmetric and symmetric signing.

With RS256, the server signs tokens using a private key and verifies them with the matching public key. The public key is, by design, public. With HS256, signing and verifying both use the same shared secret, and that secret must stay secret.

The confusion attack works like this. The attacker takes a legitimately RS256-signed token, changes the header algorithm to HS256, then signs the modified token using the server's public key as if it were the HMAC secret. If the server's verification function is told to trust the algorithm named in the header, it will now run HMAC-SHA256 using the public key it holds. The attacker also has that public key. So the attacker can produce a signature the server will accept.

The whole trick hinges on the server passing one key to a verification routine that switches behavior based on the header. The fix is the same principle as before: the server must fix the algorithm to RS256 and refuse to verify anything else, so a token claiming HS256 is rejected outright.

Brute-forcing weak HMAC secrets

When a token uses HS256, its security is entirely the strength of the shared secret. An attacker who captures a single valid token can attack the secret offline, at their own pace, with no rate limiting and no server involvement.

They take the token's signed portion and the signature, then try candidate secrets until one reproduces the signature. Standard wordlists and cracking tools make short work of common choices: secret, password, a project name, a value copied from a public tutorial, or a short random string. Once the secret is recovered, the attacker can forge any token with any claims, and nothing on the server will look wrong.

This is why a leaked or weak signing secret is a full authentication bypass. On MITRE ATT&CK, using a stolen or forged token to authenticate falls under Steal Application Access Token (T1528) and the broader Use Alternate Authentication Material technique family.

Header parameter injection: kid, jku, and x5u

The JWT header can carry more than the algorithm. Several optional parameters tell the verifier where to find the key, and a server that trusts them hands the attacker influence over verification.

  • kid (key ID) names which key to use. If the server uses that value to look up a key file or a database row without sanitizing it, the attacker can point it at a file they control, or inject path traversal and even SQL through it. A kid of ../../dev/null can make the server verify against an empty key the attacker also knows.
  • jku (JWK Set URL) tells the verifier to fetch the signing keys from a URL. If the server fetches from any URL in the header, the attacker hosts their own key set, signs the token with the matching private key, and the server fetches the attacker's public key and accepts the forgery.
  • x5u (X.509 URL) does the same with a certificate URL and carries the same risk.

The defense mirrors the algorithm rule. Do not let the token tell the server where to find its key. Resolve keys from a fixed, server-side configuration, and if you support key rotation through kid, validate it strictly against a known set rather than treating it as a path or a lookup string.

Missing or ignored claims

Even a correctly signed token can be dangerous if the server ignores the claims that bound its use.

  • No expiry. A token without an exp claim, or a server that never checks it, never dies. One leak in a log, a proxy, or browser history becomes permanent access.
  • Ignored audience and issuer. If the server does not verify aud and iss, a token minted for one service can be replayed against another that shares the same key.
  • No revocation. Because a JWT is self-contained, the server can validate it without a database lookup. That is the selling point and the weakness. Logout and password reset do nothing to a token already in the wild unless you add a server-side check.

Attack summary

AttackWhat the attacker changesRoot causePrimary defense
alg=noneSets alg to none, drops the signatureServer trusts the header's algorithmPin the algorithm server-side, reject none
Algorithm confusionSwitches RS256 to HS256, signs with the public keyVerifier chooses key type from the headerEnforce one expected algorithm
Weak secretNothing; cracks the secret offlineGuessable or leaked HMAC secretLong random secret, rotate on leak
Missing expiryReuses a token indefinitelyNo exp check or no revocationShort lifetimes plus server-side session checks
Claim confusionReplays across servicesaud and iss uncheckedValidate audience and issuer

How to defend against JWT attacks

The defenses are cheap and they compound. Apply all of them.

  1. Pin the algorithm on the server. Configure exactly which algorithm you accept and reject every token that names a different one. Do not let the incoming token's header select the verification path. This single rule kills both alg=none and RS256-to-HS256 confusion.
  2. Use strong, secret keys. For HMAC, use a long, randomly generated secret held in a secrets manager, never in source control or a tutorial default. For RSA and ECDSA, protect the private key and treat the public key as public.
  3. Validate every claim you rely on. Check exp, and check aud and iss against the values your service expects. Reject tokens that omit claims your authorization logic reads.
  4. Keep access tokens short-lived. Pair a short-lived access token with a refresh mechanism, so a leaked token is useful for minutes rather than forever.
  5. Add a revocation path. Keep a server-side session or a denylist of revoked token identifiers so logout, password reset, and incident response can actually cut off access.
  6. Use a maintained library and keep it patched. The safe defaults in modern JWT libraries exist because of exactly these attacks. Do not hand-roll verification.
See which token flaws are under active pressure

Forged and stolen tokens are a recurring theme in real-world intrusions. Our Exploit Intelligence dashboard tracks live vulnerabilities across authentication and API components, so you can see which classes of flaw are being weaponized rather than just theoretically risky.

A JWT is a good tool with a sharp edge. The token is attacker-controlled input right up until your server has confirmed the signature with an algorithm and key that you chose, and confirmed the claims say what your authorization logic needs. Fix the algorithm, protect the key, check the claims, and keep the tokens short-lived. Do those four things and the attacks in this guide have nothing left to grab.

A worked example: tracing an algorithm-confusion forgery

Walking through the confusion attack step by step makes the root cause concrete. Start with a service that issues RS256 tokens. It holds an RSA private key it never shares and publishes the matching public key so clients and other services can verify signatures. This is the intended design, and the public key being public is a feature.

The attacker begins with any valid token the service issued. They decode the header and payload, which requires no secret because both are only Base64url-encoded. They rewrite the payload to say what they want, perhaps changing a role claim to an administrative value. So far this is trivial, and on its own it produces an invalid token because the signature no longer matches.

Now the pivot. The attacker changes the header algorithm from RS256 to HS256. HS256 is symmetric, so verification uses one shared secret for both signing and checking. The attacker needs a secret that the server also holds. They already have one: the server's public key, which is published. The attacker computes an HMAC-SHA256 signature over the tampered header and payload using the public key bytes as the HMAC key.

When the token reaches the server, the flaw decides the outcome. If the verification code reads alg from the incoming header and selects its behavior from that value, it sees HS256 and runs HMAC verification. For the key it reaches for the RSA public key it has on file, because that is the key material associated with this issuer. It computes HMAC-SHA256 over the header and payload using that public key, and the result matches the attacker's signature, because the attacker used the very same bytes. The token is accepted, and the attacker is now an administrator.

The single mistake that makes every step work is letting the token's own header choose the algorithm. Pin the algorithm to RS256 on the server and a token claiming HS256 is rejected before any key is touched. This maps to MITRE ATT&CK Use Alternate Authentication Material and its sub-technique for application access tokens.

Detection signals

JWT attacks leave traces at the verification layer, so detection depends on logging what your tokens claim and how verification behaves. A few signals are worth wiring into monitoring.

Watch the header algorithm distribution. In a healthy system every token names the one algorithm you issue. Any inbound token whose header names a different algorithm, especially none or a switch between an asymmetric and a symmetric family, is a probing attempt and should be logged and counted. A rise in tokens carrying none is one of the clearest signals that someone is testing your verifier.

Watch for verification anomalies. A burst of signature-verification failures from a single client suggests an offline forgery attempt being tested against the live endpoint. Tokens that decode cleanly at the payload level but fail signature checks are the normal shape of a forgery attempt, so a spike in that specific pattern deserves attention.

Watch the optional header parameters. Requests carrying a jku or x5u pointing at an external host, or a kid containing path characters, slashes, or SQL-like syntax, indicate someone probing the key-resolution path. These parameters have narrow legitimate uses, so unusual values in them are high-signal.

SignalWhere to lookWhat it suggests
Inbound token with alg of noneAuth-layer logsalg=none forgery attempt
Algorithm switching between familiesToken header loggingAlgorithm-confusion probing
Spike in decode-ok, verify-fail tokensVerification metricsOffline forgery being tested
kid with path or query charactersHeader parameter logsInjection through key lookup
jku or x5u to an external hostHeader parameter logsAttacker-hosted key set
Expired tokens still acceptedSession and access logsMissing or ignored exp check
Log the header before you trust it

The JWT header is attacker input, and it is also a rich source of detection. Record the algorithm and any key-resolution parameters on every request before verification runs. That log costs almost nothing and turns silent forgery attempts into visible, countable events you can alert on.

Placing JWTs beside other ways of carrying identity clarifies the trade-offs that create these attacks.

PropertySigned JWTServer-side session IDOpaque bearer token
State locationIn the token, self-containedIn server storageReference to server storage
RevocationHard without extra stateImmediateImmediate
Forgery riskSignature and algorithm bugsGuessing or fixationGuessing the reference
Offline attackPossible against HMAC secretsNot applicableNot applicable
Scaling costNo lookup on validateLookup per requestLookup per request

The self-contained design that makes a JWT cheap to validate is the same property that makes revocation hard and offline forgery possible when the secret is weak. A server-side session identifier avoids both problems at the cost of a storage lookup on every request. Neither is universally better, and the JWT attacks in this guide are the price of the self-contained model, which is why the defenses focus on making the signature check impossible to bypass and adding back a revocation path.

Common misconceptions

"JWTs are encrypted, so the contents are safe." A standard signed JWT is signed, not encrypted. The header and payload are only Base64url-encoded, which anyone can decode. Never place a secret in a JWT payload expecting it to stay hidden. Encryption is a separate mechanism that has to be applied deliberately.

"The signature proves the user is who they claim." The signature proves the token was issued by something holding the key, and only if you verified it with the algorithm and key you chose. If the verifier lets the token pick its own algorithm, the signature proves nothing. Verification correctness, not the mere presence of a signature, is what carries the guarantee.

"A long random secret means I can skip the algorithm check." A strong HMAC secret defeats brute forcing, and it does nothing against alg=none or algorithm confusion, which bypass the secret entirely by changing how verification runs. You need both a strong secret and a pinned algorithm.

"Once a token is signed, it is valid until it expires and there is nothing more to do." Claims still matter. A correctly signed token with no audience check can be replayed against a sibling service sharing the key, and a signed token with no revocation path survives logout and password reset. Signature validity is one of several checks, and skipping the others leaves real gaps.

How JWT attacks evolved

The none algorithm was written into the standard for legitimate cases where the transport already guarantees integrity. Early libraries honored it by default, and because many verifiers read the algorithm from the token, changing the header to none bypassed authentication outright. The response was for libraries to stop accepting none unless explicitly configured and for guidance to insist on pinning the algorithm.

Algorithm confusion emerged from the same design pattern once developers mixed asymmetric and symmetric algorithms behind a single verification call that trusted the header. The public nature of the RSA public key, ordinarily harmless, became the forgery key. Modern libraries increasingly require the caller to state the expected algorithm and key type, which removes the ambiguity that made confusion possible.

The header-parameter attacks through kid, jku, and x5u followed as attackers looked past the algorithm to any field that influenced verification. Each one is the same lesson repeated: any part of an attacker-controlled token that steers the verification process is a vulnerability. The durable defense that came out of this history is simple to state. Decide the algorithm and the key on the server, from configuration, and let nothing in the token change either.

Frequently asked questions

Should I stop using JWTs entirely? No. A JWT with a pinned algorithm, a strong key, validated claims, a short lifetime, and a revocation path is a solid design. The attacks target misconfiguration, and a correctly configured token is hard to forge. Choose based on whether you value cheap stateless validation or immediate revocation more.

Is RS256 safer than HS256? Each is safe when used correctly. RS256 avoids sharing a secret with verifiers, which is useful when many parties verify but only one issues. HS256 is simpler when one service both issues and verifies. The algorithm-confusion attack targets systems that accept both interchangeably, so the real safety comes from accepting exactly one and rejecting the rest.

How short should token lifetimes be? Short enough that a leaked access token is useful for minutes rather than days, paired with a refresh mechanism for continuity. The exact number depends on your risk tolerance, and the principle is that a self-contained token you cannot easily revoke should not live long.

Can I revoke a JWT? Not with the token alone, since it validates without a lookup. You add revocation by keeping server-side state, such as a session record or a denylist of revoked token identifiers, and checking it during authorization. That reintroduces a lookup, which is the cost of being able to cut off access.

Where should the signing secret live? In a secrets manager or equivalent protected store, never in source control, a configuration file committed to a repository, or a tutorial default. A leaked HMAC secret is a full authentication bypass, because anyone holding it can mint valid tokens with any claims.

Does checking exp cover everything about token lifetime? Checking exp stops indefinitely reused tokens, and you should also validate aud and iss so a token minted for one service cannot be replayed against another, and add a revocation path so logout and incident response can end a session before its natural expiry.

A quick verification checklist

Before shipping any service that consumes JWTs, walk this short list against your verification code. Each item closes one of the attacks above, and together they leave nothing loose.

  1. The accepted algorithm is set in server configuration, and any token naming a different algorithm is rejected before verification. This includes an explicit rejection of none.
  2. The verification call is told which algorithm and key type to expect, so it never selects its behavior from the incoming header.
  3. The HMAC secret, if you use one, is long, randomly generated, and stored in a secrets manager. RSA and ECDSA private keys are similarly protected, and the public key is treated as public.
  4. Key resolution happens from server-side configuration. If kid is supported, its value is checked against a known set rather than used as a file path or a database lookup string, and jku and x5u from the token are ignored.
  5. Every claim your authorization logic reads is validated, including exp, aud, and iss, and a token missing a required claim is rejected.
  6. Access tokens are short-lived, and a server-side session or denylist gives you a way to revoke access before expiry.

Run through those six points and the attacks in this guide have no remaining foothold. The verification layer is small, and getting it right once protects every endpoint behind it.

Sources & further reading

Sharetwitterlinkedin

Related guides