JWT (JSON Web Token) is a compact way to transmit claims between parties. A token consists of three Base64URL-encoded parts separated by periods. This article covers JWT structure, signing algorithms, and security recommendations.
JWT structure
A token has the following form: header.payload.signature
Header
Contains the token type and signing algorithm:
{
"alg": "HS256",
"typ": "JWT"
}Payload
Claims are the data transmitted in the token. Standard fields include:
| Field | Description |
|---|---|
sub | Subject — the subject's identifier (for example, a user ID) |
iat | Issued At — creation time |
exp | Expiration — expiration time |
iss | Issuer — the token issuer |
aud | Audience — the recipient |
Example:
{
"sub": "user-123",
"iat": 1709827200,
"exp": 1709913600,
"role": "admin"
}Signature
The signature is generated as follows:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)The server verifies the signature to ensure that the token has not been altered and was issued by a trusted issuer.
Signing algorithms
| Algorithm | Type | Description |
|---|---|---|
| HS256 | Symmetric | The client and server share one secret |
| RS256 | Asymmetric | A private key is used for signing, and a public key for verification |
| ES256 | Asymmetric | ECDSA with P-256 |
RS256 is preferable for public APIs: the server stores the private key, and clients verify the signature with the public key.
Security
Do not store sensitive data in the payload
The payload is Base64-encoded, but not encrypted. Anyone can decode and read its contents. Do not include passwords, keys, or personal data.
Use a short lifetime
exp should be reasonable (minutes for an access token, hours or days for a refresh token, depending on the strategy).
Protection against interception
Transmit JWTs over HTTPS. For web applications, an httpOnly cookie is preferable to localStorage to reduce the risk of XSS.
The none algorithm
Disable support for alg: none. Algorithm-substitution attacks are possible if verification is implemented incorrectly.
Decoding and debugging
The JWT Decoder on rechecker.ru lets you paste a token and view its decoded header and payload. It is useful for debugging. Do not use it with real production tokens in public environments.
Server-side verification
Verification pseudocode:
- Split the token by
. - Decode the header and payload
- Check
exp(the token has not expired) - Check
audand, if necessary,iss - Calculate the signature and compare it with the provided signature
Use trusted libraries (for example, jsonwebtoken in Node.js), rather than a custom implementation.
Refresh tokens
An access token has a short lifetime (15–60 minutes). A refresh token has a longer lifetime and is used to obtain a new pair. Store the refresh token securely (in an httpOnly cookie or protected storage). If an access token is compromised, the damage is limited by its lifetime.
Blacklisting and revocation
By default, a JWT cannot be revoked before it expires. Immediate revocation can be handled using a blacklist (a list of revoked tokens) or a short lifetime combined with refresh tokens. A blacklist requires storage (Redis or a database) and a check on every request.
Further reading
For a practical JWT authentication implementation, see JWT authentication: a step-by-step implementation.
Payload structure
Recommended fields for an access token:
sub— user identifier (required)exp— expiration time (required)iat— issuance time (recommended)iss— issuer (for multi-tenant systems)aud— audience (if the token is for a specific service)
Add custom claims (role, permissions) as needed. Do not overload the payload—the token is sent with every request.
Base64URL
JWT uses Base64URL: the Base64 alphabet without + or /, and with = replaced by an empty string. The resulting strings are safe for URLs and headers. Do not confuse this with regular Base64.
Algorithm verification
When verifying a token, always check that the algorithm in the header matches the expected algorithm. An “alg: none” attack or substitution of HS256 with RS256 is possible if verification is implemented incorrectly. Libraries such as jsonwebtoken do this by default.
JWT in URLs
Passing a token in a query parameter is not recommended: URLs are recorded in logs and browser history. Use the Authorization: Bearer <token> header or a cookie.
Token lifetimes
Typical values: access token, 15–60 minutes; refresh token, 7–30 days. Use shorter lifetimes for highly privileged operations. The choice balances security and convenience (how often users need to log in again).
Tools
The JWT Decoder on rechecker.ru decodes the header and payload without verifying the signature. It is useful for debugging. Do not upload production tokens to public tools.
JWT vs. sessions
| Criterion | JWT | Sessions |
|---|---|---|
| State storage | Stateless (data is in the token) | Stateful (the server stores the session) |
| Scaling | Simpler (no shared storage) | Requires shared storage (Redis) |
| Revocation | More difficult (before expiration) | Simpler (delete the session) |
| Size | Sent with every request | Only the session ID |
The choice depends on the architecture. JWTs are often more convenient for microservices and distributed systems. Sessions are preferable for a monolith where immediate revocation is critical.
Attacks on JWTs
- Algorithm substitution — using
alg: noneor switching from HS256 to RS256 while substituting the key. Protection: verify the algorithm on the server. - Token theft — XSS or traffic interception. Protection: HTTPS, httpOnly cookies, and Content Security Policy.
- Weak secret — with HS256, the secret must be sufficiently long and random. Protection: at least 256 bits for HS256; RS256 is preferable.
Libraries
Popular libraries for working with JWTs:
- Node.js: jsonwebtoken
- Python: PyJWT
- Java: jjwt
- PHP: firebase/php-jwt
All of them support signature verification, exp/iat checks, and custom claims.
JWE (JSON Web Encryption)
By default, JWT does not encrypt the payload; it only signs it. For confidential data, use JWE (JSON Web Encryption), which encrypts the contents. JWE has a different structure and requires library support.
Confidentiality
Do not include personal data (email, phone number, address) in a JWT if the token could end up in logs or with third parties. Minimize the payload—include only an identifier and the necessary claims.
Time in claims
iat and exp are Unix timestamps (seconds). Account for time differences between servers. UTC is recommended everywhere.
Token length
A JWT is sent with every request. A large payload increases traffic. For APIs with frequent requests, keep the payload minimal: sub, exp, and, if necessary, role or scope.