Decoded Header
{
"alg": "HS256",
"typ": "JWT"
}
Decoded Payload
{
"sub": "1234567890",
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"iat": 1516239022,
"exp": 1735689600
}
Signature
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
The signature cannot be decoded. It is the output of an HMAC-SHA256 operation, not encoded data. To verify it, you need the secret key that was used when the token was issued.
The Three Part JWT Structure
A JWT is three Base64url encoded segments joined by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0
.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
The structure is defined in RFC 7519 and RFC 7515. Each part serves a distinct purpose:
Header
The header specifies the token type and the signing algorithm. alg tells the verifier which algorithm was used to compute the signature. typ is always JWT for standard tokens. Some tokens also include kid (key ID) to indicate which specific key was used, which matters when an auth server rotates keys.
Payload
The payload contains the claims: statements about the subject and additional metadata. Standard registered claims have short names (sub, iss, exp) to keep tokens compact. Custom claims can be anything your application needs, though the JWT specification recommends using a namespaced format (e.g., https://myapp.com/role) for custom claims to avoid collisions.
Signature
The signature is computed over base64url(header) + "." + base64url(payload) using the algorithm specified in the header. For HS256, this is HMAC-SHA256(secret, data). For RS256, it is RSA-SHA256(privateKey, data). The verifier recomputes the signature from the received header and payload and compares it to the third segment. If they do not match, the token has been tampered with.
Base64url Encoding
JWTs use Base64url, a URL safe variant of Base64. It replaces + with - and / with _, and omits the = padding characters. This makes JWT tokens safe to include in URL query parameters and HTTP headers without percent encoding.
Standard Base64 would require = padding characters and might include + or /, both of which have special meaning in URLs. Base64url avoids all of these by using only characters that need no escaping in URLs.
To manually decode the payload segment:
# The payload segment (middle part, between the two dots)
PAYLOAD="eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0"
# Add padding and decode (macOS/Linux)
python3 -c "
import base64, json, sys
s = sys.argv[1]
pad = s + '=' * (4 - len(s) % 4)
decoded = base64.urlsafe_b64decode(pad)
print(json.dumps(json.loads(decoded), indent=2))
" "$PAYLOAD"
Standard Claims Explained
| Claim | Name | Type | Description |
|---|---|---|---|
sub | Subject | string | Who the token is about. Typically a user ID. |
iss | Issuer | string | Who issued the token. Usually a URL (https://auth.example.com). |
exp | Expiration | timestamp | Unix timestamp. Token must be rejected after this time. |
iat | Issued At | timestamp | Unix timestamp. When the token was created. |
aud | Audience | string/array | Who the token is for. The API must verify it is listed. |
nbf | Not Before | timestamp | Unix timestamp. Token must not be accepted before this time. |
jti | JWT ID | string | A unique identifier for this token. Used to prevent replay. |
Not all claims are required. exp and sub are the most commonly used. The aud claim matters for services that share an auth server. Without it, a token intended for service A could be used against service B.
Decoding Does Not Mean Trusting
This is the most important thing to understand about JWT decoders. Decoding a token reveals the claims inside it. It does not tell you whether those claims are true or whether the token is legitimate.
An attacker can construct any JWT they want by hand. Without signature verification, there is no way to distinguish a genuine token from a forged one. This matters because:
-
The
alg: noneattack. Some older JWT libraries accepted tokens with"alg": "none"in the header and no signature, treating them as valid. Never accept unsigned tokens. -
Algorithm confusion attacks. If your server uses RS256 (asymmetric), an attacker might send a token with
"alg": "HS256"in the header, using your public key as the HMAC secret. Libraries with poor algorithm enforcement would verify it successfully. Always specify the expected algorithm explicitly. -
Expired tokens. Decoding reveals the
expclaim, but you still need to check whether the current time has passed it.
In production, always use a well maintained JWT library that handles verification, algorithm pinning, and expiration checking. Never implement JWT verification yourself.
Where JWTs Are Used
OAuth 2.0 / OpenID Connect
Access tokens and ID tokens in OAuth flows are frequently JWTs. The ID token in OIDC is always a JWT. Access tokens may or may not be JWTs depending on the authorization server.
API authentication
A server issues a JWT when a user logs in. The client sends it in the Authorization: Bearer <token> header with subsequent requests. The server verifies the signature and reads the claims to identify the user and check permissions.
Stateless session data
Some applications encode session state (user ID, role, preferences) in the JWT payload to avoid a database lookup on every request. The tradeoff: tokens cannot be revoked before expiration without additional infrastructure (a denylist or short expiry + refresh tokens).
Service to service authentication
Internal microservices use JWTs to authenticate calls to each other, often with short expiry times (minutes rather than hours).
Debugging JWT Issues
When a JWT authenticated request fails, a decoder is the first tool to reach for:
- Is the token expired? Check
expagainst the current Unix timestamp. - Is the audience correct? Check
audmatches what your service expects. - Is the issuer correct? Check
issmatches your auth server URL. - Are custom claims present? If your application expects a
roleorpermissionsclaim, verify they are in the payload. - Is the algorithm correct? Check
algin the header matches what your verification code expects.
Signature failures require the actual secret or public key to diagnose. A decoder cannot tell you why a signature is wrong, only what algorithm was supposedly used to create it.