What Is JWT and Why Was It Created?
JSON Web Token (JWT, pronounced "jot") is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object. It was designed to solve a specific problem: stateless authentication in distributed systems.
Before JWT, web applications typically used server-side sessions. When you logged in, the server created a session object, stored it in memory or a database, and gave you a session ID cookie. Every subsequent request sent that cookie, and the server looked up your session to verify your identity. This works fine for a single server, but becomes complex when you have multiple servers behind a load balancer. Session data needs to be shared or replicated across all servers.
JWT takes a different approach. Instead of storing session data on the server, it encodes the user's identity and permissions directly into a token that the client holds. The server signs this token cryptographically, so it can verify the token's authenticity without looking anything up. Any server with the signing key can validate the token independently. This is what makes JWT stateless: the server does not need to maintain any session state.
This stateless property made JWT the natural choice for microservices architectures, single-page applications, and mobile apps where requests might hit different backend services. It is also widely used in OAuth 2.0 and OpenID Connect, the protocols that power "Sign in with Google" and similar features.
JWT Structure: Header, Payload, and Signature
A JWT consists of three parts separated by dots: header.payload.signature. Each part is Base64URL-encoded, which is why JWTs look like long strings of random characters. But they are not random at all. You can decode any JWT by simply Base64-decoding each part.
The header is a JSON object that specifies the token type and the signing algorithm. A typical header looks like {"alg": "HS256", "typ": "JWT"}. The algorithm field is critical: it tells the receiver how to verify the signature. Common algorithms include HS256 (HMAC with SHA-256, using a shared secret) and RS256 (RSA with SHA-256, using a public/private key pair).
The payload contains the claims, which are statements about the user and additional metadata. There are three types of claims: registered claims (standardized fields like iss, sub, exp), public claims (defined in the IANA JWT Claims registry or using collision-resistant names), and private claims (custom fields agreed upon by the parties). The payload might look like {"sub": "user123", "name": "Jane Doe", "role": "admin", "exp": 1709510400}.
The signature is created by taking the encoded header, the encoded payload, a secret key, and the algorithm specified in the header, then computing the cryptographic signature. For HS256: HMAC-SHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret). The signature ensures that the token has not been tampered with. If anyone changes a single character in the header or payload, the signature verification will fail.
How JWT Authentication Flow Works
The typical JWT authentication flow has four steps. First, the user sends their credentials (username and password) to the authentication endpoint. The server verifies the credentials against the user database. If valid, the server creates a JWT containing the user's identity and relevant claims, signs it, and returns it to the client.
Second, the client stores the token. In a web browser, this is typically in memory (a JavaScript variable), localStorage, or an HTTP-only cookie. The storage choice has significant security implications, which we will discuss later.
Third, for every subsequent API request, the client includes the JWT. The standard approach is the Authorization header: "Authorization: Bearer eyJhbGci...". Some implementations use cookies instead, which are sent automatically with every request.
Fourth, the server receives the request, extracts the JWT, verifies the signature using the secret key, checks that the token has not expired (by comparing the exp claim to the current time), and reads the claims to determine the user's identity and permissions. If everything checks out, the request is processed. If the signature is invalid or the token is expired, the server returns a 401 Unauthorized response.
This flow eliminates the need for the server to store any session data. The JWT itself carries all the information needed to authenticate the user. Any server or microservice that has the signing key can independently verify the token.
Key Takeaway
The typical JWT authentication flow has four steps.
Common JWT Claims: iss, sub, exp, iat, and Custom Claims
The JWT specification defines seven registered claims, all optional but commonly used. The "iss" (issuer) claim identifies who created the token, typically the authentication server's URL. The "sub" (subject) claim identifies the user, usually a user ID or email address.
The "exp" (expiration time) claim is a Unix timestamp after which the token is no longer valid. This is arguably the most important claim because it limits the damage if a token is compromised. Short expiration times (15 minutes to 1 hour) are recommended for access tokens. The "iat" (issued at) claim records when the token was created, useful for determining token age.
The "nbf" (not before) claim specifies a time before which the token should not be accepted. The "aud" (audience) claim identifies the intended recipient of the token, preventing a token issued for one service from being used with another. The "jti" (JWT ID) claim provides a unique identifier for the token, useful for preventing replay attacks.
Beyond registered claims, applications add custom claims for their specific needs. Common custom claims include "role" (user role like admin, editor, viewer), "permissions" (an array of specific permissions), "org_id" (organization identifier in multi-tenant systems), and "email" (the user's email address). Keep the payload small because it is sent with every request. Avoid putting large data structures or sensitive information in claims, since the payload is only Base64-encoded, not encrypted. Anyone with the token can read the claims.
Security Considerations: Storage, Expiry, and Refresh Tokens
JWT security is a nuanced topic with real-world consequences. The first major decision is where to store the token on the client side. LocalStorage is accessible to any JavaScript on the page, making it vulnerable to Cross-Site Scripting (XSS) attacks. If an attacker can inject a script, they can read the token. HTTP-only cookies are not accessible to JavaScript, which mitigates XSS, but they require CSRF protection. In-memory storage (a JavaScript variable) is the most secure but tokens are lost on page refresh.
Token expiration strategy is critical. Short-lived access tokens (15 minutes) limit the window of exposure if a token is stolen. But forcing users to log in every 15 minutes is terrible UX. The solution is refresh tokens: a long-lived token (days or weeks) stored securely (usually an HTTP-only cookie) that can be exchanged for a new access token. The refresh endpoint issues a new access token only if the refresh token is valid and not revoked.
Token revocation is JWT's fundamental limitation. Because JWTs are stateless, the server cannot invalidate a specific token before it expires. If a user logs out or their account is compromised, any valid JWT they hold continues to work until it expires. Workarounds include keeping a server-side blocklist of revoked token IDs (which partially defeats the stateless benefit) or keeping access token lifetimes very short.
Never use the "none" algorithm in production. A well-known attack involves changing the algorithm in the header to "none" and stripping the signature. Vulnerable libraries accept this as a valid unsigned token. Always validate the algorithm on the server side and reject unexpected values. Similarly, never put the signing secret in a place where client-side code can access it.
Key Takeaway
JWT security is a nuanced topic with real-world consequences.
Debugging JWTs: Reading and Validating Tokens
When something goes wrong with JWT authentication, the ability to quickly inspect a token is essential. Because the header and payload are just Base64URL-encoded JSON, decoding them is straightforward. A JWT decoder tool splits the token into its three parts, decodes each one, and displays the JSON in a readable format.
Common issues you can diagnose by inspecting a JWT include: expired tokens (compare the exp claim to the current Unix timestamp), incorrect issuer or audience claims (the token was created by or intended for a different service), missing required claims (a middleware expects a role claim that is not present), and clock skew (the server's clock differs from the issuing server's clock, causing valid tokens to appear expired).
The signature cannot be verified by a decoder alone because it requires the signing key. However, seeing the algorithm in the header tells you what type of key is needed. HS256 uses a shared secret, while RS256 uses a public/private key pair. If you have access to the key, libraries like jsonwebtoken (Node.js), PyJWT (Python), or java-jwt (Java) can verify signatures programmatically.
A practical debugging workflow looks like this: copy the token from the Authorization header or browser storage, paste it into a decoder, check the expiration time, verify the claims match what you expect, and test the signature if you have the key. This process resolves the majority of authentication issues in minutes rather than hours. When building applications, logging the decoded token (minus the signature) on authentication failures provides invaluable debugging information for your team.
Try these tools
Related articles
JSON Explained: Formatting, Validating, and Converting for Developers
A comprehensive guide to JSON: syntax rules, common errors, formatting tools, JSON Schema validation, and converting between JSON and CSV.
Understanding Base64, URL Encoding, and Data Formats
Learn how Base64, URL encoding, and HTML entities work, when to use each one, and how encoding differs from encryption.
Regular Expressions for Beginners: A Practical Guide
Learn regular expression fundamentals, from basic syntax and character classes to practical patterns for matching emails, URLs, and phone numbers.