Understanding JWT Tokens: A Complete Guide

· 12 min read

Table of Contents

What Are JSON Web Tokens (JWT)?

JSON Web Tokens (JWT) have become the de facto standard for securely transmitting information between parties in modern web applications. Defined by RFC 7519, JWTs provide a compact, URL-safe method for representing claims to be transferred between two parties.

Unlike traditional session-based authentication where the server maintains state, JWTs are self-contained. This means the token itself carries all the information needed to verify a user's identity and permissions. This stateless nature makes JWTs particularly valuable in distributed systems, microservices architectures, and mobile applications.

JWTs serve two primary purposes in modern applications:

The beauty of JWTs lies in their simplicity and versatility. They work seamlessly across different programming languages and platforms, making them ideal for heterogeneous environments where your frontend might be in React, your backend in Node.js, and your mobile app in Swift or Kotlin.

Pro tip: While JWTs are incredibly useful, they're not a silver bullet for all authentication scenarios. Understanding when to use JWTs versus traditional sessions is crucial for building secure, scalable applications.

Detailed JWT Structure and Anatomy

A JWT consists of three distinct parts separated by dots (.), forming a structure that looks like this: xxxxx.yyyyy.zzzzz. Each section serves a specific purpose and together they create a tamper-evident package of information.

The Header

The header typically consists of two parts: the type of token (JWT) and the signing algorithm being used, such as HMAC SHA256 or RSA. This information tells the receiving party how to validate the token's signature.

{
  "alg": "HS256",
  "typ": "JWT"
}

The header is then Base64Url encoded to form the first part of the JWT. Common algorithms you'll encounter include:

The Payload

The payload contains the claims, which are statements about an entity (typically the user) and additional metadata. Claims are categorized into three types:

Registered claims: These are predefined claims that are not mandatory but recommended to provide interoperability. They include:

Public claims: These are custom claims that you define for your application. To avoid collisions, they should be defined in the IANA JSON Web Token Registry or use collision-resistant names (like namespaced URIs).

Private claims: Custom claims created to share information between parties that agree on using them. These are neither registered nor public claims.

{
  "sub": "1234567890",
  "name": "John Doe",
  "email": "[email protected]",
  "role": "admin",
  "iat": 1516239022,
  "exp": 1516242622
}

The payload is then Base64Url encoded to form the second part of the JWT.

Quick tip: Never store sensitive information like passwords or credit card numbers in JWT payloads. While JWTs are signed, they are not encrypted by default, meaning anyone can decode and read the payload.

The Signature

The signature is created by taking the encoded header, encoded payload, a secret (for HMAC algorithms) or private key (for RSA/ECDSA), and signing them using the algorithm specified in the header.

For example, if using HMAC SHA256, the signature would be created like this:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

The signature ensures that the token hasn't been altered. If someone changes even a single character in the header or payload, the signature verification will fail.

Complete JWT Example

When you put all three parts together, you get a complete JWT that looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

You can decode and inspect any JWT using our JWT Decoder Tool to see its header, payload, and verify its signature.

Creating and Utilizing JWTs in Practice

Creating JWTs is straightforward with the right libraries. Let's explore how to generate and use JWTs across different programming languages and scenarios.

Creating JWTs in Node.js

The jsonwebtoken library is the most popular choice for Node.js applications:

const jwt = require('jsonwebtoken');

// Create a token
const payload = {
  sub: user.id,
  email: user.email,
  role: user.role
};

const secret = process.env.JWT_SECRET;
const options = {
  expiresIn: '1h',
  issuer: 'myapp.com'
};

const token = jwt.sign(payload, secret, options);

// Verify a token
try {
  const decoded = jwt.verify(token, secret);
  console.log(decoded);
} catch (error) {
  console.error('Invalid token:', error.message);
}

Creating JWTs in Python

Python developers typically use the PyJWT library:

import jwt
import datetime

# Create a token
payload = {
    'sub': user.id,
    'email': user.email,
    'role': user.role,
    'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1),
    'iat': datetime.datetime.utcnow()
}

secret = os.environ.get('JWT_SECRET')
token = jwt.encode(payload, secret, algorithm='HS256')

# Verify a token
try:
    decoded = jwt.decode(token, secret, algorithms=['HS256'])
    print(decoded)
except jwt.ExpiredSignatureError:
    print('Token has expired')
except jwt.InvalidTokenError:
    print('Invalid token')

Creating JWTs in Java

For Java applications, the java-jwt library from Auth0 is widely used:

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

// Create a token
Algorithm algorithm = Algorithm.HMAC256(secret);
String token = JWT.create()
    .withSubject(user.getId())
    .withClaim("email", user.getEmail())
    .withClaim("role", user.getRole())
    .withExpiresAt(new Date(System.currentTimeMillis() + 3600000))
    .withIssuer("myapp.com")
    .sign(algorithm);

// Verify a token
try {
    DecodedJWT jwt = JWT.require(algorithm)
        .withIssuer("myapp.com")
        .build()
        .verify(token);
} catch (Exception e) {
    System.out.println("Invalid token: " + e.getMessage());
}

Best Practices for Token Generation

When creating JWTs, follow these guidelines to ensure security and reliability:

Pro tip: Consider implementing a token refresh mechanism using refresh tokens. This allows you to keep access tokens short-lived for security while maintaining a good user experience without frequent re-authentication.

JWT Authentication Workflow Explained

Understanding how JWTs flow through your application is crucial for implementing them correctly. Let's walk through a typical authentication workflow step by step.

Initial Authentication

  1. User submits credentials: The user sends their username and password to the authentication endpoint (typically POST /api/auth/login).
  2. Server validates credentials: The server checks the credentials against the database, verifying the password hash matches.
  3. Server generates JWT: Upon successful validation, the server creates a JWT containing the user's identity and relevant claims.
  4. Server returns token: The JWT is sent back to the client, typically in the response body. Some implementations also set it as an HTTP-only cookie.

Subsequent Requests

  1. Client includes token: For each subsequent request, the client includes the JWT in the Authorization header: Authorization: Bearer <token>
  2. Server validates token: The server extracts the token, verifies its signature, and checks its expiration.
  3. Server processes request: If the token is valid, the server extracts the user information from the payload and processes the request accordingly.
  4. Server returns response: The requested data or operation result is returned to the client.

Token Refresh Flow

When the access token expires, the refresh flow kicks in:

  1. Client detects expiration: The client receives a 401 Unauthorized response or checks the token's exp claim.
  2. Client sends refresh token: The client sends the refresh token to a dedicated refresh endpoint (POST /api/auth/refresh).
  3. Server validates refresh token: The server verifies the refresh token and checks if it's been revoked.
  4. Server issues new access token: A new access token is generated and returned to the client.
  5. Client retries original request: The client uses the new access token to retry the failed request.

Logout Flow

Logging out with JWTs requires special consideration since tokens are stateless:

  1. Client initiates logout: The user clicks logout, triggering a request to POST /api/auth/logout.
  2. Server invalidates refresh token: The server adds the refresh token to a revocation list or deletes it from the database.
  3. Client discards tokens: The client removes both access and refresh tokens from storage.
  4. Client redirects: The user is redirected to the login page or public area.
Token Type Typical Lifespan Storage Location Purpose
Access Token 15 min - 1 hour Memory or sessionStorage Authorize API requests
Refresh Token 7 days - 30 days HTTP-only cookie or secure storage Obtain new access tokens
ID Token Same as access token Memory User identity information (OpenID Connect)

Quick tip: Implement automatic token refresh in your client application to handle token expiration seamlessly. This prevents users from being unexpectedly logged out during active sessions.

Security Best Practices for JWT Implementation

While JWTs are secure by design, improper implementation can introduce vulnerabilities. Here are essential security practices to follow.

Secret Management

Your JWT secret is the foundation of your token security. Treat it like a password:

We use cookies for analytics. By continuing, you agree to our Privacy Policy.