A JSON Web Token carries a header that names the algorithm used to sign it, and that small detail is the root of two classic forgery attacks. If your backend trusts the algorithm the token declares, an attacker can set it to "none" and strip the signature, or switch an asymmetric algorithm to a symmetric one and sign with your public key. Both produce a token your server happily accepts as valid, letting the attacker impersonate any user. The fix is the same idea in both cases: never let the token tell you how to verify it. Here is how these JWT verification attacks work and how to validate tokens safely on your mobile app's backend.
Short answer
JWT forgery attacks exploit a backend that trusts the algorithm declared in the token's header. Per OWASP, the two classic cases are the alg: none attack, where an attacker sets the algorithm to none and removes the signature, and the server accepts the unsigned token; and algorithm confusion, where an attacker switches an asymmetric algorithm like RS256 to a symmetric one like HS256 and signs the token using your public key as the HMAC secret, which the server then verifies with that same public key. Both let an attacker forge tokens. The defense is to specify and enforce the expected algorithm and key on the server, never trusting the token's alg header, reject none, verify the signature, and validate the claims.
What you should know
- The JWT header declares the algorithm: trusting it is the root flaw.
alg: noneforges unsigned tokens: if the server accepts no signature.- Algorithm confusion swaps RS256 for HS256: signing with the public key.
- The fix is to pin the algorithm server-side: do not trust the header.
- Verify the signature and validate claims: signature, expiry, issuer, audience.
How is JWT verification attacked?
By exploiting how the server decides to verify a token. The table lists the main attacks.
| Attack | How it works |
|---|---|
alg: none | Set algorithm to none, remove the signature; server accepts it |
| Algorithm confusion | Switch RS256 to HS256, sign with the public key |
| No signature verification | The server decodes but never verifies |
| Weak HMAC secret | A short or guessable secret is brute-forced |
| Missing claim checks | Expiry, issuer, or audience not validated |
The unifying theme is that JWT attacks target verification, not the token format itself. The most direct is alg: none: the standard defines an option for an unsigned token, and if the server honors a token that declares none and has no signature, an attacker can craft any payload and have it accepted. Algorithm confusion is subtler, switching the declared algorithm from asymmetric to symmetric to abuse how the verification key is used. Beyond these, some backends decode a JWT without actually verifying its signature, which accepts anything, and others use an HMAC secret weak enough to brute-force, or fail to check claims like expiry, issuer, and audience, so an expired or misdirected token is accepted. Each is a server-side verification gap, which is where JWT security lives.
How do alg:none and algorithm confusion work?
Both come from the server trusting the token's declared algorithm. In the alg: none attack, the JWT header says the algorithm is none, meaning unsigned, and the token carries no signature; if the verification code accepts that, perhaps because it reads the algorithm from the header and dispatches accordingly, then a token with an attacker-chosen payload and no signature is treated as authentic, so the attacker forges whatever they want. Algorithm confusion exploits the difference between asymmetric and symmetric signing. With an asymmetric algorithm like RS256, the server signs with a private key and verifies with a public key, and that public key is, by design, public. If the server's verification picks the algorithm from the token header, an attacker can change the header to a symmetric algorithm like HS256, which verifies by recomputing an HMAC with a shared secret, and sign their forged token using the public key as that secret. Because the server then verifies HS256 using the public key it holds, the very key that is not supposed to be secret, the forged token validates. In both attacks, the flaw is that the token was allowed to dictate how it would be verified.
How do you verify JWTs safely?
Decide the algorithm and key on the server, and verify everything. The core rule is to never trust the alg header: configure your verification to expect a specific algorithm, or a fixed allowlist, and the corresponding key, and reject any token that does not match, rather than letting the token's header select the verification method. This single discipline closes both alg: none, since none is simply not an accepted algorithm, and algorithm confusion, since the server will only ever verify with the algorithm and key you pinned. Always verify the signature, never merely decode the token, and use a well-maintained JWT library configured to enforce the expected algorithm rather than one that infers it from the header. Use strong keys: a long, random HMAC secret for symmetric algorithms so it cannot be brute-forced, and proper key management for asymmetric keys. And validate the claims, not just the signature: check expiry, issuer, and audience, so an expired, foreign, or misdirected token is rejected. Keep all of this on the server, since the app is one client and an attacker submits tokens directly. The principle is that the server, not the token, decides the algorithm and key, and the server verifies the signature and the claims before trusting anything inside.
What to watch out for
The first trap is verification code that reads the algorithm from the token header and dispatches on it, which enables both alg: none and algorithm confusion; pin the algorithm and key server-side. The second is decoding a JWT without verifying its signature, or using a weak HMAC secret. The third is checking the signature but not the claims, accepting expired or misdirected tokens. JWT verification is server-side, so a pre-submission scan such as PTKD.com (https://ptkd.com), which reads the binary against OWASP MASVS, surfaces how your app handles authentication and tokens as the surface to protect, while the verification logic, pinning the algorithm and validating claims, is yours to implement on the backend.
What to take away
- JWT forgery attacks exploit a backend that trusts the algorithm in the token header:
alg: nonestrips the signature, and algorithm confusion switches RS256 to HS256 and signs with the public key. - Both, plus skipped signature verification, weak secrets, and missing claim checks, are server-side verification gaps that let an attacker forge tokens and impersonate users.
- Verify safely by pinning the expected algorithm and key server-side, never trusting the
algheader, verifying the signature, using strong keys, and validating expiry, issuer, and audience. - JWT verification is server-side; use a pre-submission scan such as PTKD.com to surface your app's authentication and token handling, and enforce strict verification on the backend.

