Three segments, joined by dots
A JWT looks like xxx.yyy.zzz — a header, a payload, and a signature, each base64url-encoded. The signature covers the encoded header and payload, so tampering with either invalidates the token.
Build the header
A JSON object describing the token type and the signing algorithm.
typis always"JWT". Commonalgvalues:HS256(shared secret),RS256(RSA public/private),ES256(ECDSA).{ "alg": "HS256", "typ": "JWT", "kid": "2024-01" // optional key id }
Then base64url-encode the JSON to produce the first segment.
Build the payload (claims)
The payload carries the claims. Use registered claims for things consumers already understand, and add custom claims for your application.
{ "iss": "https://auth.acme.com", "sub": "usr_01HXYZ", "aud": "api.acme.com", "iat": 1716500000, "exp": 1716503600, // 1 hour later "scope": "read:docs write:docs" }
Base64url-encode this JSON to produce the second segment.
Don't put secrets in the payload. It is encoded, not encrypted — anyone who has the token can read every claim.
Sign the encoded header + payload
Concatenate the two encoded segments with a dot, then run the signing algorithm over that string. Base64url-encode the result to produce the third segment.
// pseudocode signingInput = base64url(headerJson) + "." + base64url(payloadJson) signature = HMAC_SHA256(signingInput, secret) // HS256 // or RSA_SHA256(signingInput, privateKey) // RS256 jwt = signingInput + "." + base64url(signature)
That's it — you have a JWT. In practice, never build this from scratch. Use a vetted library so algorithm handling, key parsing, and constant-time comparison are correct.
Code examples
All examples sign the same payload with HS256, expiring in 1 hour. See Libraries for the rest.
// npm install jsonwebtoken import jwt from "jsonwebtoken"; const token = jwt.sign( { sub: "usr_01HXYZ", scope: "read:docs", }, process.env.JWT_SECRET, { algorithm: "HS256", issuer: "https://auth.acme.com", audience: "api.acme.com", expiresIn: "1h", } );
Verifying a token
Decoding is not verifying. Anyone can decode any JWT. Before trusting a token you must:
- Validate the signature with the expected algorithm and key — never accept
algfrom the token alone. - Check
expandnbfagainst the current clock (allow a small ±60s skew). - Check
issandaudmatch what your service expects. - Reject anything you don't recognise — unknown
alg, missing claims, oversized tokens.
See Best Practices and Security for the full checklist.