JWT Attacks: The Complete Practical Guide
Anatomy of a JWT: Header, Payload, and Signature
How a JWT is built: the three base64url parts, the header params that attacks abuse (alg, kid, jwk, jku), the common claims, and how the signature is computed.
Published
Before you can attack a JWT you need to read one fluently. The structure is simple and the same every time, and once you can pick out the algorithm, the claims, and the dangerous header parameters at a glance, every attack in this cluster reads clearly. This page is the map. Practice decoding on a free live target.
Three parts, two dots
A JWT is three base64url-encoded strings joined by dots:
header.payload.signatureA real token looks like this (line-wrapped for readability, it is one string):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3aWVuZXIiLCJyb2xlIjoidXNlciJ9.VK9xT...signature...Each part is independently base64url-decodable. The dots are separators, not part of the encoding. Decode the first part and you get the header, decode the second and you get the payload. The third is the signature, which is raw bytes rather than readable JSON.
base64url, not base64
The parts use base64url, a URL-safe variant of base64. Two differences matter when you decode by hand:
+becomes-and/becomes_.- The trailing
=padding is stripped.
So to decode a part with a standard base64 tool, translate the alphabet back and re-add padding first:
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' | tr '_-' '/+' | base64 -d
# {"alg":"HS256","typ":"JWT"}Skipping that step is the usual reason a quick decode "fails." Most JWT tooling handles base64url for you.
The header
The header is a small JSON object describing how the token is signed. The two standard fields:
{ "alg": "HS256", "typ": "JWT" }algis the signing algorithm, for exampleHS256(HMAC) orRS256(RSA). This field is the target of two different attacks: setting it tonone, and algorithm confusion.typis the token type, usuallyJWT.
The header can also carry optional parameters that tell the server which key to use to verify the token. These are the ones later spokes abuse:
kida key id, used to look up the verifying key by name.jwkan embedded public key, inside the token itself.jkua URL pointing at a JSON Web Key Set (JWKS) of keys.x5u/x5cthe certificate equivalents ofjkuandjwk.
If a server trusts any of these to choose the key, a token can nominate a key the attacker controls. That is covered in JWK, JKU, and KID injection.
The payload
The payload is a JSON object of claims, the statements the server trusts once the signature checks out:
{
"sub": "wiener",
"role": "user",
"iat": 1700000000,
"exp": 1700003600
}Common registered claims:
subthe subject, typically the user id or name.iatissued-at, a timestamp.expexpiry, after which the token should be rejected.nbfnot-before, a time until which the token is not yet valid.issandaudthe issuer and intended audience.
Plus whatever application claims the app adds, like role, admin, or tenant. Those application claims are what you rewrite once you can sign, and exp/nbf are what you tamper with to keep a token alive. Note the payload is readable, so these are never secret.
The signature
The signature is what makes the token tamper-evident. For HS256 it is an HMAC computed over the encoded header and payload joined by a dot, keyed with the server's secret:
signature = HMAC-SHA256( base64url(header) + "." + base64url(payload), secret )For RS256 it is an RSA signature over the same input, made with the server's private key and verified with its public key. Either way, the signature covers the header and payload, so if you change a single claim the signature no longer matches, and a server that verifies properly rejects the token. The entire JWT attack surface is about defeating that check: skipping it, recovering the key, or tricking the server into using a key you control.
Reading a token by eye
With the structure in hand, your first move against any token is always the same: split on the dots, base64url-decode the first two parts, and look. What algorithm is in the header? Is there a kid, jwk, or jku? What claims are in the payload, and which one decides your privileges? That thirty-second read tells you which attack to try first.
From here, go to flawed signature verification for the fastest bug, or weak secret brute force if the token is HS256. Or decode a live one now on a free target.