JWT: JSON Web Token Complete Guide

A JWT is a compact, self-contained token used to securely transmit information.

JWT: JSON Web Token

JWT (JSON Web Token) is the most popular format for stateless authentication tokens. It encodes user identity and claims into a compact, self-contained, and digitally signed string that can be verified without a database lookup, making it well suited to APIs, single-page applications, and distributed microservice architectures.

What Is a JWT

A JSON Web Token is a standardised string defined by RFC 7519, composed of three Base64URL-encoded parts joined by dots. It is commonly used in token-based authentication. After a user logs in, the server issues a JWT containing claims about the user's identity and permissions. The client stores this token and sends it with every subsequent request to protected endpoints. The server can verify the token's authenticity and read its claims without consulting a database, because all the necessary information is contained within the token itself and protected by a cryptographic signature.

JWT format:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjQyLCJyb2xlIjoiYWRtaW4ifQ.s5JK9iF0_8...
header.payload.signature — each section is Base64URL-encoded separately

JWT Structure: Three Parts

Every JWT consists of exactly three sections separated by dots. Each section serves a distinct purpose, and together they provide both the data the server needs and the proof that the data has not been tampered with.

PartContentExample (decoded)
HeaderDeclares the token type and the signing algorithm used to generate the signature{"alg": "HS256", "typ": "JWT"}
PayloadContains claims, which are statements about the user and additional metadata. This section is readable by anyone who holds the token.{"userId": 42, "role": "admin", "exp": 1720000000}
SignatureA cryptographic signature over the header and payload that allows the server to verify the token has not been modified since it was issuedHMACSHA256(base64(header)+"."+base64(payload), secret)

The signature is what makes JWTs trustworthy. When the server receives a token, it re-computes the expected signature using the header, payload, and its own secret key. If the computed signature matches the one in the token, the payload is genuine and has not been altered. If even a single character in the payload has been changed, the signatures will not match and the server will reject the token.

Common JWT Claims

The payload section of a JWT contains claims. Some claim names are standardised by the JWT specification and carry specific meanings that libraries and frameworks recognise automatically. Others are custom claims you define for your own application needs.

ClaimNameWhat It Contains
subSubjectThe unique identifier of the user or entity the token represents, typically a user ID or UUID
issIssuerThe identifier of the service or server that created and signed the token
audAudienceThe intended recipient of the token. The server should reject tokens where its identifier does not match the audience claim.
expExpirationA Unix timestamp indicating when the token expires. Servers must reject tokens where this time has passed.
iatIssued AtA Unix timestamp recording when the token was created. Useful for calculating token age.
nbfNot BeforeA Unix timestamp before which the token must not be accepted. Useful for tokens that should become valid at a future time.
jtiJWT IDA unique identifier for the token, used to prevent replay attacks and support token blacklisting
roleCustomA custom claim for authorisation, carrying the user's role such as admin, editor, or viewer

How JWT Authentication Works

JWT authentication follows a consistent flow that separates the act of verifying credentials from the act of verifying identity on subsequent requests. The server only needs the database during login. After that, every request is verified cryptographically.

  1. The user submits their credentials, typically an email address and password, to the login endpoint
  2. The server verifies the credentials against its database and confirms the user is who they claim to be
  3. The server creates a JWT payload containing the user's ID, role, and any other relevant claims, then signs the token using its secret key or private key
  4. The server returns the signed JWT to the client in the response body
  5. The client stores the JWT, either in memory for security or in localStorage for persistence, and includes it in the Authorization header of every subsequent request to protected endpoints
  6. The server receives the request, extracts the token from the Authorization header, and verifies the signature using its secret key or public key
  7. If the signature is valid and the token has not expired, the server reads the claims from the payload and processes the request accordingly
  8. If the signature is invalid or the token has expired, the server returns a 401 Unauthorized response
How the client sends a JWT with each request:
GET /api/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Signing Algorithms: HS256 vs RS256

The algorithm declared in the JWT header determines how the signature is created and verified. Choosing the right algorithm depends on your architecture and trust model.

  • HS256 (HMAC SHA-256): Uses a single shared secret key for both signing and verification. Simple to implement and fast. Suitable when all services that need to verify the token are trusted and can securely share the secret. If the secret leaks, all previously issued tokens remain valid until they expire.
  • RS256 (RSA SHA-256): Uses an asymmetric key pair. The server signs tokens with a private key and publishes the corresponding public key so that any service can verify tokens without having access to the private key. This is the preferred choice for distributed systems, third-party integrations, and any architecture where token verification happens in services that should not have signing capability.
  • ES256 (ECDSA SHA-256): Uses elliptic curve cryptography for asymmetric signing, producing smaller signatures than RSA while offering equivalent security. Increasingly popular as an alternative to RS256 in performance-sensitive environments.
  • none: Produces no signature at all. This algorithm must never be used in production. Several early JWT libraries had vulnerabilities where they would accept tokens signed with the none algorithm, allowing attackers to forge arbitrary tokens.

JWT Security Best Practices

JWTs are secure when implemented correctly, but several common mistakes introduce serious vulnerabilities. Following these practices covers the most critical security considerations.

  • Use short expiration times for access tokens: Set the exp claim to 15 to 60 minutes for access tokens. Short-lived tokens limit the window of opportunity if a token is compromised.
  • Use refresh tokens for re-issuing access tokens: A refresh token is a long-lived, single-use credential stored securely server-side. When an access token expires, the client presents the refresh token to receive a new access token without requiring the user to log in again. Refresh tokens can be revoked immediately if needed.
  • Never use the none algorithm: Always specify and enforce a specific algorithm when verifying tokens. Reject any token that declares none as its algorithm.
  • Store tokens securely: For web applications, storing tokens in an HttpOnly cookie prevents JavaScript from accessing them and protects against XSS attacks. Storing in localStorage is convenient but exposes the token to any script running on the page. In-memory storage is the most secure option for short sessions.
  • Validate both the signature and the expiry on every request: Never skip signature verification as a performance optimisation. A token with a valid signature but an expired exp claim must be rejected.
  • Keep the payload small: The payload is sent with every request. Including only the claims you actually need keeps the token compact and reduces bandwidth overhead.
  • Never put sensitive data in the payload: The payload is Base64URL-encoded, not encrypted. Anyone who holds the token can decode and read the claims. Passwords, credit card numbers, and other sensitive data must never appear in a JWT payload.

Frequently Asked Questions

  1. Is the JWT payload encrypted?
    No. The payload is Base64URL-encoded, which is a reversible encoding that anyone can decode without any key. It provides no confidentiality. If you need to store sensitive data in a JWT, you must use JWE (JSON Web Encryption) rather than the standard JWT format, which only signs the data without encrypting it. For most authentication use cases, storing only non-sensitive identifiers like a user ID and role in the payload is sufficient.
  2. Can you invalidate a JWT before it expires?
    Not directly, because JWTs are stateless and the server does not store them after issuing. Three common strategies address this limitation. The first is maintaining a token blacklist or deny-list on the server, storing the jti claim of revoked tokens and checking it on each request. The second is using very short access token expiry times combined with refresh tokens that can be revoked server-side. The third is rotating the signing key, which immediately invalidates all previously issued tokens but also logs out all users simultaneously.
  3. What is the difference between a JWT and a session ID?
    A session ID is an opaque reference stored in the browser that points to session data held on the server. Every request requires a database or cache lookup to retrieve that data. A JWT is self-contained and carries the session data within the token itself. The server verifies the signature and reads the claims directly without a database query. JWTs scale better horizontally because any server instance can verify a token independently. Sessions are easier to invalidate because deleting the server-side record immediately revokes access.
  4. What happens when a JWT expires?
    When a JWT reaches its exp timestamp, the server must reject it and return a 401 Unauthorized response. A well-implemented client detects this 401 and automatically uses its refresh token to request a new access token from the token endpoint. If the refresh token is also expired or has been revoked, the client must prompt the user to log in again. The client should check the exp claim proactively and refresh the token shortly before expiry rather than waiting for a request to fail.
  5. Should I use JWT for session management in a traditional web application?
    JWTs are best suited to stateless API authentication, particularly for SPAs, mobile apps, and microservices where requests may be handled by any of many server instances. For traditional server-rendered web applications with a single backend, server-side sessions stored in a database or cache are often simpler to manage, easier to revoke, and avoid the complexity of refresh token rotation. The choice depends on your architecture. If horizontal scaling across many stateless API servers is a requirement, JWTs provide a clear advantage. If simplicity and instant revocation are the priority, sessions may be the better fit.

Conclusion

JWTs enable stateless, scalable authentication by storing signed user data in the token itself, allowing any server instance to verify a request without a database lookup. They are well suited to APIs, single-page applications, and microservice architectures where stateless verification and horizontal scaling are priorities. Implementing them correctly requires short access token lifetimes, secure storage, robust refresh token handling, and strict signature and expiry validation on every request. Explore OAuth, session vs token authentication, and authentication vs authorization to build a complete understanding of modern identity and access management.