A JSON Web Token (JWT) is a compact, URL-safe way to represent claims between two parties. JWTs are commonly used for authentication and authorization in web applications. When you log in to a website, the server often returns a JWT that your browser includes in subsequent requests.
JWTs are defined in RFC 7519 and are widely used in OAuth 2.0, OpenID Connect, and API authentication.
Every JWT consists of three parts separated by dots (.):
The header typically contains two fields:
{
"alg": "HS256", // Signing algorithm
"typ": "JWT" // Token type
}
Common algorithms include HS256 (HMAC-SHA256), RS256 (RSA-SHA256), and ES256 (ECDSA-SHA256).
The payload contains the claims โ statements about the user and metadata:
{
"sub": "1234567890", // Subject (user ID)
"name": "John Doe", // Custom claim
"iat": 1516239022 // Issued at (Unix timestamp)
}
The signature verifies the token hasn't been tampered with. For HS256:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Important: The header and payload are only Base64URL-encoded, NOT encrypted. Anyone can decode and read them. The signature only ensures integrity, not confidentiality.
Since JWT parts are Base64URL-encoded, you can decode them with any Base64 decoder:
.) into three parts- with + and _ with /=) if needed to make length a multiple of 4In a terminal:
# Decode the header
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' | base64 -d
# Output: {"alg":"HS256","typ":"JWT"}
# Decode the payload
echo 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ==' | base64 -d
# Output: {"sub":"1234567890","name":"John Doe","iat":1516239022}
In the browser or Node.js:
function decodeJWT(token) {
const parts = token.split('.');
if (parts.length !== 3) throw new Error('Invalid JWT');
const header = JSON.parse(atob(parts[0]
.replace(/-/g, '+').replace(/_/g, '/')));
const payload = JSON.parse(atob(parts[1]
.replace(/-/g, '+').replace(/_/g, '/')));
return { header, payload, signature: parts[2] };
}
// Usage
const token = 'eyJhbGciOiJIUzI1NiIs...';
const { header, payload } = decodeJWT(token);
console.log(payload.sub); // "1234567890"
console.log(payload.name); // "John Doe"
With the jose library for full verification:
import * as jose from 'jose';
const secret = new TextEncoder().encode('your-secret');
const { payload } = await jose.jwtVerify(token, secret);
console.log(payload); // Verified payload
import jwt # pip install PyJWT
# Decode without verification (for inspection)
payload = jwt.decode(token, options={"verify_signature": False})
print(payload)
# Decode WITH verification
payload = jwt.decode(token, "your-secret", algorithms=["HS256"])
print(payload["sub"]) # "1234567890"
For RS256 tokens (common in OAuth):
import jwt
import requests
# Fetch the public key from JWKS endpoint
jwks = requests.get("https://example.com/.well-known/jwks.json").json()
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(jwks["keys"][0])
payload = jwt.decode(token, public_key, algorithms=["RS256"],
audience="your-audience")
The JWT spec defines several registered claims:
sub (Subject) โ Unique identifier for the useriss (Issuer) โ Who issued the token (e.g., your auth server URL)aud (Audience) โ Intended recipient of the tokenexp (Expiration) โ Unix timestamp when the token expiresnbf (Not Before) โ Token is not valid before this timeiat (Issued At) โ When the token was createdjti (JWT ID) โ Unique identifier to prevent replay attacksApplications also add custom claims like name, email, role, permissions, etc.
exp and nbf claimsiss and aud claims โ Prevent token confusion attacksTry our free JWT Decoder tool โ decode any JWT token instantly in your browser
Open JWT Decoder โIf you need to quickly inspect a JWT token, use our free JWT Decoder. It runs entirely in your browser โ no data is sent to any server. Features:
For more developer tools, check out our complete tools collection with 40+ free browser-based utilities.