If you run a website that needs to identify users or protect APIs, json Web Tokens (JWT) are a common solution you’ll see recommended. JWTs are compact, self-contained tokens that carry claims about a user or client, and they are often used instead of server-side sessions because they simplify scaling and integration across services. This guide explains what JWTs are, how they actually work, when they are a good fit, and the practical security and implementation choices site owners should make to avoid common pitfalls.
What is a JWT and how it works
A JWT is simply a string made up of three parts separated by dots: a header, a payload, and a signature. The header declares the token type and signing algorithm, the payload contains claims (for example user ID, roles, or expiration time), and the signature proves the token has not been tampered with. Technically the header and payload are Base64URL-encoded JSON objects, while the signature is created by hashing the encoded header and payload with a signing key. When a client sends a JWT to your server, the server verifies the signature and checks claims like expiration and intended audience before granting access.
Common claims in a JWT
Some claims are standardized and useful for access control: “iss” (issuer), “sub” (subject, typically the user ID), “aud” (audience), “exp” (expiration time), and “iat” (issued at). In addition to these, you can add custom claims such as user roles or feature flags. Keep in mind that anyone with the token can read the payload, so do not store secrets or sensitive personal data in a JWT unless the token is encrypted.
When to use JWT vs. server sessions
JWTs shine when you need stateless authentication across multiple services or servers without a centralized session store. They are convenient for APIs, single-page applications, and microservices architectures because services can validate tokens locally if they have the signing key or public key. However, server-side sessions are straightforward and sometimes safer for traditional web apps that rely on short-lived interactions and frequent session revocation. If you require immediate logout or forced session termination, sessions with a central store can make that easier than standard JWTs unless you build additional infrastructure for token revocation.
Practical implementation steps
Implementing JWT authentication follows a predictable flow: authenticate the user (for example, check username and password), generate a token containing the necessary claims, and return it to the client. The client includes the token in subsequent requests,commonly in an Authorization: Bearer
Example flow to protect an API
A typical API flow is: user logs in -> server issues access token and optional refresh token -> client stores tokens -> client sends access token with each request -> server validates signature and claims -> if access token expired, client uses refresh token to request a new access token. Design refresh tokens carefully: they are long-lived credentials and must be stored and transmitted securely.
Token storage and security trade-offs
Where you store JWTs on the client affects your threat model. Storing tokens in localStorage or sessionStorage is easy but exposes them to cross-site scripting (XSS) attacks; a malicious script can steal the token and impersonate the user. Storing tokens in cookies can mitigate XSS if you use HttpOnly cookies, but cookies are vulnerable to cross-site request forgery (CSRF) if you do not implement protections like SameSite or CSRF tokens. A common pattern is to store access tokens in memory and use short expirations, with refresh tokens stored in secure, HttpOnly cookies. This balances theft risk and user experience, but it requires careful handling of refresh endpoints and token rotation.
Security best practices
Protecting your JWT-based system means thinking about signing keys, token lifetimes, and revocation mechanics. Use strong, secret signing keys (or asymmetric keys like RSA/ECDSA with a private key for signing and a public key for verification), rotate keys on a schedule, and publish or cache public keys for distributed verification when using asymmetric signing. Keep access tokens short-lived (minutes to an hour), and use refresh tokens to obtain new access tokens without forcing frequent logins. Implement token revocation for scenarios where immediate invalidation is required: store a token blacklist, maintain a last-password-change timestamp in the user record and compare it to a “iat” claim, or use rotating refresh tokens that are invalidated after use.
Checklist of practical controls
- Always use https to prevent token interception in transit.
- Validate signature, expiration, issuer, and audience on every request.
- Do not store sensitive secrets inside the token payload unless the token is encrypted (JWE).
- Use short-lived access tokens and longer-lived, carefully stored refresh tokens.
- Implement monitoring and alerts for unusual token use (mass logins, geographic anomalies).
Common mistakes and how to avoid them
A frequent mistake is treating JWTs like opaque session IDs and assuming they can be invalidated easily; because standard JWTs are self-contained, they remain valid until their expiry unless you add server-side checks. Another mistake is exposing the signing key or using weak algorithms like “none” or deprecated hashing modes,always use supported libraries and avoid insecure defaults. Forgetting to set token scopes or audience restrictions can cause tokens to be valid across unexpected endpoints, so be explicit about what each token is allowed to access. Lastly, underestimating XSS risks and storing long-lived tokens in accessible storage can lead to account takeover; design storage and lifetimes with attack vectors in mind.
Tools and libraries
Most languages and frameworks have mature JWT libraries: jsonwebtoken for Node.js, JWT.io lists many options, PyJWT for Python, jose for various ecosystems, and built-in support in many cloud identity providers. If you operate microservices, consider a centralized identity provider (Auth0, Okta, Firebase Auth, AWS Cognito, or a self-hosted OpenID Connect provider) which can handle signing, rotation, revocation endpoints, and tokens for you. Using a proven provider reduces operational overhead but requires evaluating vendor trust and cost.
When not to use JWT
If your application requires immediate session revocation for every action, relies heavily on server-side session data, or you are building a simple site with low scaling needs, classic server sessions stored in a database or cache may be a better fit. JWTs add complexity when you need fine-grained control over each session, because you must design additional infrastructure for revocation, introspection, or token blacklists to match the control a session store gives by default.
Summary
JWTs are a useful tool for stateless authentication and cross-service identity, but they are not a one-size-fits-all solution. Use them when you need scalable, self-contained tokens for APIs and distributed systems, apply short lifetimes for access tokens with secure refresh strategies, and protect keys and storage to reduce exposure. Validate claims on every request, use HTTPS, and prefer established libraries or identity providers rather than building one-off implementations. Thoughtful choices about storage, expiration, and revocation will keep your users safe while enabling a smooth authentication experience.
FAQs
1. Is a JWT encrypted? Can someone read its contents?
By default a JWT is signed but not encrypted, which means anyone who obtains it can read the payload but cannot change it without breaking the signature. If you need confidentiality, use JSON Web Encryption (JWE) or avoid putting sensitive data inside tokens and keep secrets server-side.
2. Should I store JWTs in localStorage or cookies?
There is no one-size-fits-all answer. localStorage exposes tokens to XSS attacks, while cookies can protect tokens with HttpOnly but require CSRF protections. Many implementations keep short-lived access tokens in memory and use secure, HttpOnly cookies for refresh tokens. Evaluate your app’s XSS and CSRF risk and choose storage that minimizes exposure.
3. How do I revoke a JWT before it expires?
Since JWTs are self-contained, you must add server-side controls for revocation: maintain a blacklist of token IDs, track a user’s last valid timestamp and reject tokens issued before it, or use short-lived access tokens with revocable refresh tokens so you can cut off new access quickly. Choose a strategy that balances performance and control.
4. Can I use JWT for everything (sessions, API, SSO)?
JWTs are well suited to APIs and single sign-on, but for traditional session management on a single server, simple sessions may be easier and safer. For critical security requirements and immediate revocation needs, sessions with a server-side store provide stronger control unless you build complementary revocation mechanisms for JWTs.
5. What signing algorithm should I use?
Use a modern, secure algorithm supported by your libraries. Asymmetric algorithms like RS256 or ES256 are popular because they let you distribute public keys to services for verification while keeping the private key safe. If you use symmetric signing (HS256), protect and rotate the secret key carefully. Avoid insecure options or using “none”.