1. What is a JWT?
A JWT (JSON Web Token) is a compact, URL-safe string that carries a set of claims between two parties and is protected by a digital signature or a MAC. It is defined in RFC 7519 and is most often used to prove who a user is after login. The server can trust the claims because it can verify the signature, without looking anything up in a database.
2. What are the three parts of a JWT?
A JWT has a header, a payload and a signature, each Base64URL-encoded and joined by dots as header.payload.signature. The header says which algorithm and token type are used, the payload holds the claims, and the signature proves the first two parts were not changed. The header and payload are only encoded, not encrypted, so anyone can read them.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjMiLCJleHAiOjE3NjAwMDAwMDB9
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
3. How does JWT authentication work?
After the user logs in with valid credentials, the server creates a signed JWT and returns it to the client. The client sends it on every later request, usually in the Authorization: Bearer <token> header. The server verifies the signature and expiry, then trusts the claims, such as the user ID, without needing a server-side session lookup.
4. Is a JWT encrypted? Can anyone read the payload?
No, a standard signed JWT (a JWS) is only Base64URL-encoded, so anyone who holds it can decode and read the payload. The signature protects integrity, not confidentiality. Never put passwords, secrets or sensitive personal data in it. If you need confidentiality, use an encrypted JWT (JWE) or keep the data on the server.
5. What is the difference between HS256 and RS256?
HS256 is a symmetric algorithm (HMAC with SHA-256) where one shared secret both signs and verifies tokens. RS256 is asymmetric (RSA with SHA-256): a private key signs and a public key verifies. RS256 is better when many services or third parties must verify tokens, because they never need the signing key. HS256 is simpler and faster for a single application.
6. What is the difference between an access token and a refresh token?
An access token is a short-lived credential sent with each API request, while a refresh token is a longer-lived credential used only to obtain new access tokens. Keeping the access token short, often 5 to 15 minutes, limits the damage if it leaks. The refresh token is sent only to the auth endpoint, can be stored more securely, and can be revoked on the server.
7. Where should you store a JWT on the client: localStorage or cookies?
For browsers, an HttpOnly, Secure, SameSite cookie is generally safer, because JavaScript cannot read it, so an XSS attack cannot steal the token. localStorage is readable by any script on the page, so one XSS bug exposes the token. Cookies need CSRF protection, which SameSite and CSRF tokens provide. Mobile apps should use the platform secure storage, such as Keychain or Keystore.
8. What are JWT claims? What are registered, public and private claims?
Claims are the key-value statements in the JWT payload about the user or the token. Registered claims are standard names defined in RFC 7519, such as iss, sub, aud, exp, nbf, iat and jti. Public claims are custom names registered with IANA or made collision-resistant, for example with a URL. Private claims are custom names agreed between parties, such as role or tenantId.
{
"sub": "user-123",
"iss": "https://auth.example.com",
"aud": "my-api",
"exp": 1760000000,
"role": "admin"
}
9. What does the `exp` claim do and how should you set token expiry?
The exp claim is a Unix timestamp in seconds after which the token must be rejected. Servers must check it during verification. Keep access tokens short-lived, typically minutes, and use refresh tokens for longer sessions. A short expiry is the main way to limit damage from a stolen token, since a plain JWT cannot be recalled before it expires.
10. What is the difference between JWT and session-based authentication?
With sessions, the server stores session data and gives the client only a session ID, usually in a cookie, which it looks up on each request. With JWTs, the token itself carries the claims and is verified by signature, so the server stores nothing. Sessions are easy to revoke and keep data private, while JWTs scale across servers more easily but are harder to invalidate.
11. What does stateless authentication mean, and what are its pros and cons?
Stateless means the server keeps no per-user session state, because each request carries a self-contained, verifiable token. This makes horizontal scaling simple and suits microservices and APIs. The downsides are that tokens are hard to revoke before expiry, they can grow large as claims are added, and changes to a user's permissions only apply once a new token is issued.
12. How does the server verify a JWT?
The server recomputes or checks the signature using the expected algorithm and key, then validates the claims. It should confirm the signature is valid, exp and nbf are acceptable, iss matches the expected issuer and aud matches the API. The algorithm must be pinned on the server, not trusted from the header.
const jwt = require('jsonwebtoken');
const payload = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
issuer: 'https://auth.example.com',
audience: 'my-api',
});
13. What is the "alg: none" attack and how do you prevent it?
The attack sets the header alg to none and removes the signature, hoping a naive server accepts the token as unsigned and trusts forged claims. Prevent it by always specifying the allowed algorithms explicitly when verifying, never letting the token choose, and rejecting none. Use a maintained library and keep it updated.
14. What is the algorithm confusion attack (RS256 to HS256)?
In this attack, the attacker changes the header from RS256 to HS256 and signs the token using the server's public key as the HMAC secret. If the server picks the algorithm from the header, it verifies with that public key as an HMAC key and accepts the forgery. The fix is to pin the allowed algorithms per key, so a public key can never be used as an HMAC secret.
15. How do you revoke or invalidate a JWT before it expires?
Because JWTs are stateless, you need extra server state to revoke them. Common approaches are a denylist of revoked token IDs (jti) kept in a fast store like Redis until the tokens expire, short access token lifetimes, and revoking refresh tokens so no new access tokens are issued. Another is a per-user token version that is checked on each request.
16. How do you implement logout with JWTs?
Since the server holds no session, logout mainly means the client deletes its tokens. For real security, the server should also revoke the refresh token and, if needed, add the access token's jti to a denylist until it expires. Clearing an HttpOnly cookie is done by the server sending a response that expires it.
17. How does the refresh token flow work?
When the access token expires and an API returns 401, the client sends the refresh token to the token endpoint and receives a new access token, and often a new refresh token. With refresh token rotation, each refresh token can be used once, and reuse of an old one signals theft and revokes the whole token family. Refresh tokens are usually stored server-side or hashed so they can be revoked.
18. How can XSS attacks affect JWTs?
Cross-site scripting lets an attacker run JavaScript in your page, which can read tokens from localStorage or sessionStorage and send them to the attacker's server. Using HttpOnly cookies stops scripts from reading the token, although XSS can still make requests on the user's behalf. The real defence is preventing XSS with output escaping, a strict Content Security Policy and sanitised input.
19. How can CSRF attacks affect JWTs, and how do you prevent them?
CSRF only applies when the browser sends the token automatically, which happens when it is stored in a cookie. A malicious site can then trigger a request that carries it. Prevent this with SameSite=Lax or Strict cookies, anti-CSRF tokens, and checking the Origin header. If the JWT is sent manually in the Authorization header, classic CSRF is not a concern.
20. How do you send a JWT to an API? What is the Bearer scheme?
The standard way is the Authorization header using the Bearer scheme, defined in RFC 6750: Authorization: Bearer <token>. The word Bearer means whoever holds the token can use it, so it must be sent over HTTPS only. Avoid putting tokens in URLs, since they end up in logs and browser history.
fetch('/api/profile', {
headers: { Authorization: `Bearer ${accessToken}` },
});
21. What is OAuth 2.0, and how does it relate to JWT?
OAuth 2.0 is an authorization framework that lets an app get limited access to a resource on a user's behalf without seeing their password. It defines roles and flows but does not mandate a token format. JWT is simply a common format for OAuth access tokens, and it is required for OpenID Connect ID tokens. They are different things: OAuth is a protocol, JWT is a token format.
22. What is OpenID Connect and how does it differ from OAuth 2.0?
OpenID Connect (OIDC) is an identity layer built on top of OAuth 2.0 that adds authentication. OAuth 2.0 answers what an app may access, while OIDC answers who the user is. OIDC adds an ID token, which is a JWT containing claims such as sub, iss, aud and exp, plus a userinfo endpoint and standard scopes like openid and profile.
23. What is the difference between an ID token and an access token?
An ID token is a JWT meant for the client application to learn who the user authenticated as, and its audience is the client. An access token is meant for a resource server or API to authorize requests, and its audience is that API. Clients should not send ID tokens to APIs, and APIs should not accept them as access tokens.
24. What is the difference between authentication and authorization, and where do JWTs fit?
Authentication proves who you are, while authorization decides what you are allowed to do. A JWT is issued after authentication and carries identity claims such as sub. It can also carry authorization data such as scope or role, which the API checks per request. Sensitive permissions should still be checked on the server against current data.
25. What are the main security best practices when using JWTs?
Use HTTPS everywhere, pin allowed algorithms, and use strong secrets or keys. Validate exp, iss and aud, keep access tokens short-lived, and store tokens safely, preferably in HttpOnly cookies on the web. Do not put sensitive data in the payload, rotate signing keys using kid, and have a plan for revocation such as refresh token rotation.
26. What is a JWKS endpoint and how do clients use it to verify tokens?
A JWKS (JSON Web Key Set) endpoint is a URL, usually /.well-known/jwks.json, that publishes the public keys an issuer uses to sign tokens. A verifier reads the kid from the token header, finds the matching key in the set, and verifies the signature with it. Cache the key set and refetch only when an unknown kid appears, so key rotation works without a deploy.
{
"keys": [
{ "kty": "RSA", "kid": "2026-10", "use": "sig", "alg": "RS256", "n": "0vx7...", "e": "AQAB" }
]
}
27. How would you rotate signing keys without logging every user out?
Publish the new public key in the JWKS alongside the old one, start signing new tokens with the new key and a new kid, and keep the old public key until every token it signed has expired. Verifiers pick the key by kid, so both generations validate during the overlap. Only then remove the old key. The overlap should be at least as long as the longest token lifetime.
28. What is the `kid` header and why can it be dangerous?
The kid (key ID) header tells the verifier which key was used to sign the token, which enables rotation. It is dangerous because it is attacker-controlled input. If a server uses it in a file path or SQL query to look up the key, it can lead to path traversal or SQL injection. Treat kid as an opaque lookup into a fixed allowlist of known keys and never build queries from it.
29. What is wrong with this code that decodes a token and trusts it?
The code uses jwt.decode, which only Base64URL-decodes the token and never checks the signature, so anyone can forge a token with any claims. Always use jwt.verify (or the library's equivalent) with pinned algorithms before trusting any claim. Use decode only to peek at data such as the kid before verification.
const user = jwt.decode(token);
if (user.role === 'admin') grantAccess();
const user = jwt.verify(token, key, { algorithms: ['HS256'] });
30. Why should you validate the `aud` claim, and what happens if you skip it?
The aud claim names the service a token is intended for, and each API should accept only tokens whose audience matches itself. If you skip the check, a token issued for one service can be replayed against another service that trusts the same issuer, which is a cross-service confused deputy problem. This matters most with shared identity providers and microservices, where one signing key issues tokens for many APIs.
31. What is clock skew and how do you handle it when validating `exp` and `nbf`?
Clock skew is the small time difference between the machine that issued a token and the one that verifies it. A token with nbf slightly in the future may be rejected, or an expired one briefly accepted. Most libraries accept a small leeway, typically 30 to 60 seconds, and you should keep servers synced with NTP. Do not set a large tolerance, because it extends the effective lifetime of every token.
jwt.verify(token, key, { algorithms: ['RS256'], clockTolerance: 30 });
32. How strong must an HS256 secret be, and what is the risk of a weak one?
An HS256 secret should be at least 256 bits (32 random bytes) generated by a cryptographically secure random generator, never a word or a password. A weak secret can be brute-forced offline with tools like hashcat, because the attacker only needs one valid token to test guesses. Once cracked, they can mint tokens for any user. Store the secret in a secrets manager, not in source code.
33. How would you design a JWT-based auth system for microservices?
Have one authentication service sign tokens with an asymmetric algorithm such as RS256 or ES256, and publish its public keys through a JWKS endpoint. Each microservice verifies the signature locally, checking iss, aud and exp, so no call to the auth service is needed per request. Keep access tokens short-lived, and put coarse claims like scope in the token while checking fine-grained permissions against fresh data. For service-to-service calls, use separate tokens with narrow audiences.
34. What are the trade-offs of putting roles and permissions inside a JWT?
Putting roles in the token makes authorization checks fast and database-free, but the data goes stale until the token expires. If an admin removes a role, the user keeps it for the rest of the token's life. Large permission lists also bloat every request header. A good compromise is to include only coarse roles or scopes, keep tokens short-lived, and check sensitive actions against current data.
35. How large can a JWT get, and what problems does a big token cause?
There is no limit in the spec, but practical limits exist: cookies are capped at about 4 KB, and many servers and proxies reject headers above 8 KB, often with a 431 or 400 error. Large tokens also cost bandwidth on every request. Avoid embedding big permission arrays, profile data or group lists. Keep claims minimal and reference a server-side lookup by sub for anything else.
36. What is a JWE and when would you use it instead of a plain JWT?
A JWE (JSON Web Encryption) is an encrypted token with five dot-separated parts, so its claims are unreadable without the decryption key, unlike a signed JWS. Use it when the payload must stay confidential from the client or intermediaries, for example when passing sensitive data through a browser. In practice many teams avoid the need by keeping sensitive data on the server and sending only an identifier. Often the best choice is signing then encrypting, a nested JWT.
37. What is a JWT `jti` claim and how does it help prevent replay attacks?
The jti (JWT ID) is a unique identifier for a single token. The server can store the jti of tokens it has seen or revoked, and reject any token whose ID is repeated or on a denylist. This is useful for one-time tokens such as password reset or email verification links, and for revocation. Store entries only until the token's exp, so the store does not grow forever.
const payload = jwt.verify(token, key, { algorithms: ['HS256'] });
if (await redis.get(`used:${payload.jti}`)) throw new Error('replayed');
await redis.set(`used:${payload.jti}`, 1, 'EXAT', payload.exp);
38. How does DPoP or token binding protect against stolen bearer tokens?
Bearer tokens work for anyone who holds them, so a stolen one is fully usable. DPoP (RFC 9449) binds the access token to a key pair held by the client: each request carries a short-lived signed proof JWT, and the token contains a thumbprint of the client's public key. A thief who copies only the token cannot produce valid proofs without the private key. Mutual TLS (RFC 8705) offers a similar sender-constrained approach.
39. A user changes their password. How do you invalidate their existing JWTs?
Because tokens are stateless, you need a server-side signal. The simplest approach is to store a tokenVersion or passwordChangedAt value per user and include it as a claim, then reject tokens whose value is older. Also revoke all of the user's refresh tokens. This adds one cheap lookup per request, usually from a cache, but it gives you reliable invalidation without a full denylist.
40. Is it safe to put a JWT in a URL query string? What are the alternatives?
No, because URLs are saved in server logs, proxy logs, browser history and Referer headers, so the token leaks to many places. Use the Authorization header or an HttpOnly cookie instead. When a browser cannot set headers, such as with WebSockets or file downloads, exchange the JWT for a very short-lived, single-use ticket and put only that ticket in the URL.
41. How do you secure WebSocket connections with JWTs?
Browsers cannot set custom headers on a WebSocket handshake, so common options are sending the token in an HttpOnly cookie, passing a short-lived single-use ticket in the query string, or sending the JWT as the first message after the connection opens. Validate the token during the handshake or before processing any messages. Since the connection can outlive the token, also decide how to handle expiry, for example by closing the socket or requiring a refresh message.
42. Should you use JWTs for user sessions in a traditional web app?
Often no. For a single server-rendered application, a regular server-side session with an opaque ID is simpler, easier to revoke and exposes no claims to the client. JWTs shine when many independent services or third parties must verify identity without sharing a session store. Choosing JWTs only because they are popular usually adds revocation and size problems for no real benefit.
43. What is the difference between ES256 and RS256, and which should you choose?
Both are asymmetric signature algorithms. RS256 uses RSA and ES256 uses ECDSA with the P-256 curve. ES256 gives similar security with much smaller keys and signatures (64 bytes versus 256 bytes for RSA-2048), which keeps tokens shorter and signing faster. RS256 has the widest compatibility and faster verification. EdDSA (Ed25519) is a modern alternative. Choose ES256 for new systems when all your libraries support it, and RS256 for maximum interoperability.
44. Why can `localStorage` plus a long-lived JWT be a bad combination, and what would you do instead?
A long-lived token in localStorage can be stolen by any XSS bug and then reused from anywhere until it expires, with no way to recall it. Instead, keep the access token short-lived and in memory, and store the refresh token in an HttpOnly, Secure, SameSite cookie restricted to the refresh endpoint. Rotate refresh tokens on use so a stolen one is detected and revoked.
45. What is the output of this code, and why?
It throws TokenExpiredError: jwt expired, because expiresIn: 1 sets exp to one second after issuing and the wait is two seconds. The library compares exp with the current time during verify, and a rejected token never returns a payload. Code that calls verify must handle the error, and a middleware should turn it into a 401 response so the client can refresh.
const token = jwt.sign({ sub: '1' }, 'secret', { expiresIn: 1 });
setTimeout(() => {
try {
jwt.verify(token, 'secret');
} catch (e) {
console.log(e.name, e.message);
}
}, 2000);
46. How would you handle token expiry on the client for a seamless user experience?
Use an HTTP interceptor that catches 401 responses, calls the refresh endpoint once, and retries the original request with the new access token. Make sure concurrent failing requests share a single in-flight refresh promise so you do not fire many refresh calls or trigger refresh token reuse detection. If the refresh itself fails, clear state and send the user to the login page.
let refreshing = null;
async function getFreshToken() {
refreshing ??= fetch('/auth/refresh', { method: 'POST', credentials: 'include' })
.then(r => r.json())
.finally(() => { refreshing = null; });
return refreshing;
}
47. What is the performance cost of verifying JWTs, and how do you reduce it?
HMAC verification is very fast (microseconds), RSA verification is fast, and ECDSA verification is slower than RSA but still cheap compared with a database call. The real costs are fetching keys and parsing large tokens. Cache the JWKS in memory, reuse the parsed key object instead of re-importing it on each request, and keep payloads small. Signing is costlier than verifying, particularly with RSA, so avoid issuing tokens unnecessarily.
48. How do you test JWT-protected endpoints and what edge cases should you cover?
Generate tokens in tests with a known test key, then call the endpoint with each variant. Cover a valid token, an expired token, one with a wrong signature, wrong iss or aud, a missing token, a malformed string, an alg: none token and an HS256 token signed with the public key. Each invalid case should return 401 (or 403 for insufficient scope) and never a server error. These tests catch the verification mistakes behind most real JWT vulnerabilities.
49. What is the difference between a 401 and a 403 response in a JWT-secured API?
Return 401 Unauthorized when the token is missing, malformed, expired or has an invalid signature, meaning the caller is not authenticated. Return 403 Forbidden when the token is valid but the user lacks permission for the resource, such as a missing scope. Send a WWW-Authenticate: Bearer header with a 401, and an error="invalid_token" hint if useful, so clients know to refresh or log in again.
50. What is JWT best used for, and when should you avoid it?
JWTs are best for short-lived, verifiable claims passed between systems: API access tokens, OIDC ID tokens, single sign-on assertions and one-time links such as email verification. Avoid them as a general-purpose long-lived session store, for data that must be revoked instantly, for large or sensitive data, or when a simple server-side session would do. The question to ask is whether stateless verification is worth losing easy revocation.