The Complete Guide to JWT Tokens for Web Developers
JSON Web Tokens (JWTs) are one of the most widely used standards in modern web authentication. Whether you're building a REST API, implementing single sign-on across microservices, or integrating with third-party OAuth providers, JWTs are almost certainly part of your stack. Yet many developers use them without fully understanding how they work, what their limitations are, or how to avoid the security pitfalls that come with token-based authentication. This guide breaks down everything you need to know about JWTs — from their internal structure to real-world security best practices.
What is a JWT Token?
A JSON Web Token (JWT, pronounced "jot") is an open standard (RFC 7519) that defines a compact, self-contained way to securely transmit information between parties as a JSON object. The specification was introduced in 2015 and has since become the de facto standard for stateless authentication on the web.
Unlike traditional session-based authentication where the server stores session data in memory or a database, JWTs carry all the information needed to verify a user's identity right inside the token itself. The server doesn't need to look anything up — it just validates the token's signature and reads the claims.
JWTs are used in a wide range of scenarios:
- Authentication — the most common use case. After a user logs in, the server issues a JWT that the client includes with every subsequent request to prove its identity.
- API authorization — APIs use JWTs to verify that a request comes from an authorized client and to determine what resources that client can access based on the token's claims.
- Single Sign-On (SSO) — JWTs enable users to authenticate once and access multiple services without logging in again. Identity providers like Auth0, Okta, and Firebase all issue JWTs.
- Information exchange — because JWTs can be signed (and optionally encrypted), they provide a trustworthy way to pass claims between services in a microservices architecture.
The Three Parts of a JWT
Every JWT consists of three parts separated by dots: the header, the payload, and the signature. When you see a JWT in the wild, it looks like this:
xxxxx.yyyyy.zzzzz Header: xxxxx (base64url-encoded JSON) Payload: yyyyy (base64url-encoded JSON) Signature: zzzzz (cryptographic signature)
Let's examine each part in detail:
1. Header
The header is a JSON object that describes the token type and the signing algorithm being used. It typically looks like this:
{
"alg": "HS256",
"typ": "JWT"
}The alg field specifies the algorithm used to sign the token — commonly HS256 (HMAC with SHA-256) for symmetric signing or RS256 (RSA with SHA-256) for asymmetric signing. The typ field is always JWT. This JSON object is then Base64url-encoded to form the first part of the token.
2. Payload
The payload contains the claims — statements about the user and additional metadata. Claims are the actual data the token carries. Here's an example payload:
{
"sub": "1234567890",
"name": "Jane Developer",
"email": "jane@example.com",
"role": "admin",
"iat": 1709550000,
"exp": 1709553600
}Important: the payload is Base64url-encoded, not encrypted. Anyone who has the token can decode the payload and read its contents. Never store sensitive information like passwords, credit card numbers, or secret keys in the JWT payload.
3. Signature
The signature is what makes a JWT trustworthy. It's created by taking the encoded header, the encoded payload, a secret key (or private key), and the algorithm specified in the header, then signing that data. For HMAC SHA-256, the formula is:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
When the server receives a JWT, it recalculates the signature using the same algorithm and secret. If the recalculated signature matches the one in the token, the server knows the token hasn't been tampered with. If even a single character in the header or payload was modified, the signature won't match, and the token is rejected. For asymmetric algorithms like RS256, the token is signed with a private key and verified with the corresponding public key — useful when the verifier and issuer are different services.
Common JWT Claims Explained
The JWT specification defines a set of standard (registered) claims. These are not mandatory, but they're widely recognized and recommended for interoperability:
- iss (Issuer) — identifies the principal that issued the JWT. Typically a URL or identifier for your auth server, e.g.,
https://auth.example.com. - sub (Subject) — identifies the subject of the JWT, usually the user ID. This is the primary identifier that tells your application which user this token represents.
- aud (Audience) — identifies the recipients that the JWT is intended for. A token with
aud: "api.example.com"should be rejected by any service that isn'tapi.example.com. - exp (Expiration Time) — the Unix timestamp after which the token must not be accepted. This is critical for security — tokens without an expiration live forever if stolen.
- iat (Issued At) — the Unix timestamp at which the token was issued. Useful for determining the age of a token and implementing policies like "reject tokens older than 24 hours."
- nbf (Not Before) — the Unix timestamp before which the token must not be accepted. This allows you to issue a token that becomes valid at a future time.
- jti (JWT ID) — a unique identifier for the token. Useful for preventing token replay attacks by keeping a server-side list of used token IDs.
Beyond these standard claims, you can include any custom claims your application needs — user roles, permissions, tenant IDs, subscription tiers, and more. Just remember: every claim increases the token size, and the payload is readable by anyone, so keep custom claims minimal and non-sensitive.
How JWT Authentication Works
Understanding the full authentication flow is essential for implementing JWTs correctly. Here's the step-by-step process:
- Step 1: User logs in — the client sends credentials (username/password, OAuth code, etc.) to the authentication server.
- Step 2: Server creates JWT — after verifying the credentials, the server creates a JWT containing the user's identity and any relevant claims, signs it with a secret key, and returns it to the client.
- Step 3: Client stores the token — the client stores the JWT, typically in an httpOnly cookie (preferred for security) or in memory. Avoid storing JWTs in localStorage, as it's vulnerable to XSS attacks.
- Step 4: Client sends token with requests — for every subsequent API request, the client includes the JWT in the
Authorizationheader using the Bearer scheme:Authorization: Bearer <token>. - Step 5: Server validates the token — the server extracts the JWT from the header, verifies the signature using the secret key, checks that the token hasn't expired (via the
expclaim), and reads the claims to determine who the user is and what they're allowed to do. - Step 6: Server responds — if the token is valid, the server processes the request and returns the appropriate response. If the token is invalid or expired, the server returns a 401 Unauthorized error.
Why stateless authentication matters
Because the JWT contains all the information needed to authenticate the user, the server doesn't need to query a database or session store on every request. This makes JWT authentication inherently scalable — you can add more server instances behind a load balancer without worrying about session affinity or shared session stores. Any server with the signing secret can validate any token.
Access Tokens vs Refresh Tokens
In production authentication systems, you'll almost always work with two types of tokens: access tokens and refresh tokens. They serve different purposes and have different lifetimes.
- Access tokens — short-lived JWTs (typically 5 to 30 minutes) that grant access to protected resources. Their short lifespan limits the damage if one is stolen. The client includes the access token in the Authorization header with every API request.
- Refresh tokens — longer-lived tokens (days to weeks) used exclusively to obtain new access tokens. They're stored securely (httpOnly cookie) and are only sent to the token endpoint, never to resource APIs. Refresh tokens can be opaque strings — they don't need to be JWTs.
The refresh flow works as follows: when the access token expires, the client sends the refresh token to the auth server's token endpoint. The server validates the refresh token, and if it's still valid, issues a new access token (and optionally a new refresh token). The client then uses the new access token for subsequent requests.
Refresh token rotation
Token rotation is a security best practice where each time a refresh token is used, it's invalidated and a new one is issued alongside the new access token. If an attacker steals a refresh token and tries to use it after the legitimate user has already rotated it, the server detects the reuse, revokes the entire token family, and forces the user to re-authenticate. This significantly limits the window of vulnerability for stolen refresh tokens.
JWT Security Best Practices
JWTs are secure when implemented correctly, but they have a unique set of risks that developers must understand. Follow these best practices to keep your JWT implementation safe:
- Always use HTTPS — JWTs are bearer tokens, meaning anyone who possesses the token can use it. Transmitting tokens over plain HTTP exposes them to interception via man-in-the-middle attacks. HTTPS is non-negotiable.
- Keep expiration times short — access tokens should expire in 5 to 30 minutes. The shorter the lifetime, the smaller the window of opportunity for an attacker who steals a token. Use refresh tokens for long-lived sessions.
- Never store sensitive data in the payload — the JWT payload is Base64url-encoded, not encrypted. Anyone can decode it. Never include passwords, payment details, social security numbers, or any personally identifiable information beyond what's strictly necessary for authorization.
- Always validate the signature server-side — never trust a JWT without verifying its signature. Client-side validation is not sufficient because the client can be compromised. Your API must verify the signature on every request before trusting any claims.
- Use httpOnly cookies for storage — storing JWTs in
localStorageorsessionStoragemakes them accessible to JavaScript and vulnerable to XSS attacks. httpOnly cookies cannot be read by JavaScript, providing a strong layer of protection. - Validate all claims — always check
exp,iss, andaudclaims. Checking only the signature is not enough — an unexpired, validly signed token intended for a different service should still be rejected. - Use strong signing secrets — for HMAC algorithms, use a secret that's at least 256 bits (32 bytes) of cryptographically random data. Never use simple strings like "secret" or "password" as your JWT signing key.
Common JWT Vulnerabilities
Understanding common attack vectors helps you build more robust authentication systems. Here are the JWT vulnerabilities every developer should know about:
- Algorithm confusion (alg: none) — in early JWT library implementations, an attacker could set the header's
algfield tononeand strip the signature entirely. The server would accept the token without any verification. Modern libraries rejectalg: noneby default, but you should always explicitly specify allowed algorithms in your verification code rather than trusting the token's header. - Algorithm switching (RS256 to HS256) — if a server is configured to accept RS256 tokens (asymmetric), an attacker might craft a token with
alg: HS256and sign it using the server's public key (which is often publicly available) as the HMAC secret. If the library naively uses the algorithm from the header, it would verify the token successfully. Always enforce the expected algorithm server-side. - Token theft via XSS — if a JWT is stored in localStorage and your application has a cross-site scripting vulnerability, an attacker can inject JavaScript that reads the token and sends it to their server. This is why httpOnly cookies are the recommended storage mechanism.
- Lack of revocation — since JWTs are stateless, there's no built-in way to invalidate a token before it expires. If a user logs out or their account is compromised, the token remains valid until expiration. Mitigations include short token lifetimes, token blacklists (at the cost of statefulness), and refresh token rotation.
- JWT vs sessions: the tradeoff — JWTs trade revocability for scalability. Session-based auth lets you instantly revoke access by deleting the session from the store, but requires server-side state. JWTs scale effortlessly across distributed systems but can't be revoked without additional infrastructure. Many production systems use a hybrid approach: JWTs for short-lived access tokens with a server-side blacklist for critical revocation scenarios.
Mitigation checklist
- Explicitly whitelist allowed algorithms — never rely on the token's
algheader - Reject tokens with
alg: none - Store tokens in httpOnly, Secure, SameSite cookies
- Keep access token lifetime under 15 minutes
- Implement refresh token rotation with reuse detection
- Maintain a server-side blacklist for emergency revocation
How to Decode a JWT
Because JWT headers and payloads are simply Base64url-encoded JSON, you can decode them without knowing the signing secret. This is by design — the signature protects integrity, not confidentiality. Decoding a JWT is useful for debugging authentication issues, inspecting token claims, checking expiration times, and understanding what data your tokens carry.
Base64url encoding is a URL-safe variant of standard Base64. The differences are minimal: the + character is replaced with -, / is replaced with _, and padding = characters are omitted. To decode a JWT manually, you would split the token by dots, Base64url-decode the first two parts, and parse the resulting JSON strings.
In practice, you don't need to do this manually. Our JWT Decoder tool lets you paste any JWT and instantly see the decoded header, payload, and signature information — with color-coded sections, claim descriptions, and expiration status. Everything runs locally in your browser, so your tokens never leave your device.
If you also need to work with Base64 encoding directly — for example, to encode API keys, decode webhook payloads, or inspect binary data — our Base64 Encoder/Decoder handles standard Base64, Base64url, and hex conversions. And if you're implementing JWT signature verification or need to generate HMAC digests for testing, the Hash Generator supports SHA-256, SHA-384, SHA-512, and other algorithms commonly used in JWT signing.
Decode & Inspect JWT Tokens Instantly
Paste any JWT to see its decoded header, payload, and signature verification status. Check expiration times, inspect claims, and debug authentication issues — all processed locally in your browser with zero data sent to any server.
Key Takeaways
- A JWT is a self-contained token with three Base64url-encoded parts: a header (algorithm and type), a payload (claims about the user), and a cryptographic signature that ensures the token hasn't been tampered with.
- Standard claims like
exp,iss,sub, andaudshould always be validated server-side — not just the signature. - Use short-lived access tokens (5-30 minutes) paired with refresh tokens for long-lived sessions. Implement refresh token rotation with reuse detection for maximum security.
- The JWT payload is encoded, not encrypted. Never store passwords, payment details, or other sensitive data in token claims.
- Store JWTs in httpOnly, Secure, SameSite cookies — never in localStorage. This protects against XSS-based token theft.
- Always enforce allowed algorithms server-side to prevent algorithm confusion attacks. Never trust the
algfield in the token header without validation. - JWTs trade instant revocability for stateless scalability. For production systems, plan a revocation strategy — whether it's short token lifetimes, blacklists, or a hybrid approach.
- Use browser-based tools like a JWT Decoder to inspect and debug tokens safely, without exposing them to third-party servers.
Try These Free Tools
Related Articles
5 Free Online Tools Every Developer Needs
Discover the essential free online tools that every developer should bookmark — from JSON formatting and regex testing to Base64 encoding and UUID generation.
Why You Need a Strong Password (And How to Generate One)
Most passwords are cracked in seconds. Learn how hackers break passwords, what makes a strong password, and how to generate uncrackable passwords instantly.
JSON Formatting and Validation: A Developer's Quick Guide
A practical guide to JSON formatting, validation, and common mistakes. Learn JSON best practices and how to convert between JSON and CSV quickly.