What Is a JSON Web Token (JWT)?
A JSON Web Token (JWT, pronounced "jot") is a compact, URL-safe standard for securely transmitting information between parties as a JSON object. It is defined by RFC 7519 and has become the de facto standard for authentication and authorization in modern web applications, single-page apps (SPAs), and microservice architectures.
JWTs are self-contained — all the necessary information is stored within the token itself. This eliminates the need for server-side session storage, making them ideal for stateless authentication. When a user logs in, the server issues a JWT, which the client stores and sends with each subsequent request. The server validates the token's signature without needing to query a database.
A JWT looks like a long, encoded string divided into three parts by periods:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
This three-part structure — Header, Payload, and Signature — is the foundation of every JWT. Understanding each part is essential for debugging, security auditing, and building authentication systems.
Anatomy of a JWT
The Header
The header specifies the token type and the signing algorithm. It is a JSON object encoded in Base64URL:
{
"alg": "HS256",
"typ": "JWT"
}
The alg (algorithm) field tells the verifier which cryptographic algorithm was used to sign the token. Common algorithms include:
HS256— HMAC using SHA-256 (symmetric, shared secret)HS384— HMAC using SHA-384HS512— HMAC using SHA-512RS256— RSA SSA PKCS1 using SHA-256 (asymmetric, public/private key pair)RS512— RSA using SHA-512ES256— ECDSA using P-256 curve and SHA-256PS256— RSA-PSS using SHA-256
The typ (type) field is usually JWT. It is optional but recommended for clarity.
The Payload
The payload contains the claims — statements about an entity (usually the user) and additional metadata. Like the header, it is a JSON object encoded in Base64URL:
{
"sub": "1234567890",
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622,
"iss": "myapp.com"
}
JWT claims are divided into three categories:
Registered claims: Predefined claims with specific meanings. These are optional but recommended:
iss(issuer) — Who created the tokensub(subject) — The subject of the token (usually user ID)aud(audience) — Intended recipientsexp(expiration) — Token expiration time (Unix timestamp)nbf(not before) — Token is not valid before this timeiat(issued at) — When the token was createdjti(JWT ID) — Unique identifier for the token
Public claims: Custom claims defined by the JWT specification or agreed upon by parties using the token. These should be registered in the IANA JSON Web Token Registry to avoid collisions.
Private claims: Custom claims specific to your application, such as role, permissions, tenant_id, or any other data your application needs.
The Signature
The signature is the most critical part of the JWT. It is created by encoding the header and payload, concatenating them with a period, and signing the result with the specified algorithm:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
The signature ensures that the token has not been tampered with. If even a single character in the header or payload is changed, the signature becomes invalid. However, the signature does not encrypt the data — anyone with the token can decode the header and payload and read the claims.
How to Decode a JWT
Decoding a JWT is straightforward because the header and payload are simply Base64URL-encoded JSON. You can decode them with any Base64 decoder, or use a dedicated tool. To quickly decode and inspect any JWT, use RiseTop's free JWT Decoder — paste your token and instantly see the decoded header, payload, and signature information.
Here is how to decode a JWT manually in various programming languages:
// JavaScript (browser or Node.js)
function decodeJWT(token) {
const [header, payload] = token.split('.');
return {
header: JSON.parse(atob(header.replace(/-/g, '+').replace(/_/g, '/'))),
payload: JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')))
};
}
// Python
import base64, json
def decode_jwt(token):
header, payload = token.split('.')[:2]
def decode_part(part):
padding = 4 - len(part) % 4
part += '=' * padding
return json.loads(base64.urlsafe_b64decode(part))
return {'header': decode_part(header), 'payload': decode_part(payload)}
Note that Base64URL differs slightly from standard Base64: + is replaced with -, / is replaced with _, and trailing padding = is removed. Your decoding function must account for these differences.
JWT vs. Session-Based Authentication
Traditional session-based authentication stores user state on the server (usually in a database or in-memory store like Redis). The client receives a session cookie containing only a session ID. Each request requires the server to look up the session data.
JWT-based authentication, by contrast, stores all necessary information in the token itself. The server validates the token's signature without needing to look up any session data. This makes JWTs particularly well-suited for:
- Microservices: Services can independently verify tokens without sharing a session store
- Mobile apps: Tokens are easily stored and sent with API requests
- Single-page applications: No need for cookie-based session management
- Third-party API access: Tokens can be scoped and time-limited
However, JWTs also have trade-offs. They cannot be easily revoked (unlike server-side sessions, which can be deleted), and the token size increases with each additional claim. Large JWTs can impact performance when sent with every request.
Common JWT Security Pitfalls
Storing Sensitive Data in the Payload
The payload is Base64URL-encoded, not encrypted. Anyone who intercepts the token can decode it and read all claims. Never store passwords, credit card numbers, Social Security numbers, or any other sensitive data in the JWT payload. If you need to transmit sensitive information, use JWE (JSON Web Encryption) instead.
Accepting the "none" Algorithm
The none algorithm means the token has no signature. Some implementations mistakenly accept tokens with "alg": "none", effectively disabling signature verification. Always explicitly specify the allowed algorithms and reject none in production.
Weak Signing Secrets
For HMAC-based algorithms (HS256, HS384, HS512), the secret key must be sufficiently long and random. A short or predictable secret can be brute-forced. Use at least 256 bits (32 bytes) of cryptographically random data for HS256.
Missing Token Expiration
Tokens without an exp claim remain valid forever. Always set a reasonable expiration time. If a token is compromised without an expiration, the attacker has indefinite access. Short-lived access tokens (5-15 minutes) combined with longer-lived refresh tokens are the recommended approach.
Not Validating All Claims
When verifying a token, don't just check the signature. Also validate the exp (not expired), nbf (not before), iss (expected issuer), aud (expected audience), and sub (expected subject). Skipping claim validation is a common source of security vulnerabilities.
JWT Use Cases in Modern Applications
API Authentication
Most REST APIs use JWTs for authentication. The client sends the token in the Authorization: Bearer <token> header with each request. The API validates the token and extracts user information from the claims.
OAuth 2.0 and OpenID Connect
OAuth 2.0 uses JWTs as access tokens and ID tokens. OpenID Connect, built on top of OAuth 2.0, uses JWTs extensively for identity federation. Google, GitHub, and other identity providers issue JWTs as ID tokens that contain user profile information.
Single Sign-On (SSO)
JWTs enable SSO across multiple applications within an organization. A user authenticates once with a central identity provider and receives a JWT that is accepted by all connected applications without requiring separate logins.
Best Practices for Working with JWTs
- Use HTTPS exclusively — never transmit tokens over unencrypted connections
- Set short expiration times for access tokens (5-15 minutes)
- Implement refresh token rotation for long-lived sessions
- Store tokens securely on the client (HttpOnly cookies preferred over localStorage)
- Validate all claims, not just the signature
- Use asymmetric algorithms (RS256, ES256) when multiple services need to verify tokens
- Include the
jticlaim for token revocation tracking - Keep signing keys rotated regularly
Frequently Asked Questions
Can JWT tokens be decrypted?
No — JWT tokens are not encrypted. The header and payload are Base64URL-encoded, which anyone can decode. The signature ensures integrity (the token hasn't been modified) but does not provide confidentiality. For sensitive data, use JWE (JSON Web Encryption) instead of JWS.
What is the difference between JWT, JWS, and JWE?
JWT is the umbrella standard. JWS (JSON Web Signature) is a signed JWT — the most common type, where data is encoded and cryptographically signed. JWE (JSON Web Encryption) encrypts the payload so it can only be read by parties with the decryption key. Most applications use JWS.
How do I verify a JWT signature?
You need the signing key: a shared secret for HMAC algorithms, or the public key for RSA/ECDSA. Split the token into three parts, then use the algorithm specified in the header to verify that the signature matches the base64url-encoded header + payload. Libraries like jsonwebtoken (Node.js) or PyJWT (Python) handle this automatically.
What information is stored in a JWT payload?
The payload contains claims: sub (user ID), name, email, role, iat (issued at), exp (expiration), iss (issuer), and aud (audience) are common. Custom claims for application-specific data like permissions, tenant IDs, or feature flags can also be included.
How long should a JWT token be valid?
Access tokens should be short-lived — 5 to 15 minutes is standard. This limits the damage if a token is compromised. Refresh tokens can last days or weeks and are used to obtain new access tokens without requiring the user to re-authenticate.
Conclusion
JWTs have become an integral part of modern web authentication, offering a stateless, scalable approach to managing user sessions and API access. Understanding their three-part structure — header, payload, and signature — is essential for any developer working with authenticated systems. Remember that JWTs encode, not encrypt, so never store sensitive data in the payload. Always validate signatures, set expiration times, and follow security best practices.
To quickly decode and inspect any JWT, try RiseTop's free JWT Decoder. Paste your token, and the tool instantly decodes the header, payload, and provides detailed claim information.