What Is a JSON Web Token (JWT) & How Does It Work?

Learning Objectives

  • Understand what JWT stands for and its role in modern web authentication
  • Explain the three components of a JWT and their purposes
  • Analyze JWT-based authentication flows in REST APIs
  • Decode and inspect JWTs to understand their contents
  • Recognize when JWT is the right authentication strategy for your application

Introduction

What is JWT? JWT stands for JSON Web Token, a secure way to send information between a client and a server in modern web applications. Authentication used to mean sessions stored on servers, cookies tracking user state, and databases queried on every request. That architecture worked fine when applications lived on a single server, but modern distributed systems—microservices, mobile apps, single-page applications—needed something different.

Think of a JWT like a digital passport. When you log in, the server issues you a cryptographically signed token containing your identity and permissions. Every subsequent request includes this token, and the server can verify its authenticity without looking up session data in a database. The token itself carries everything the server needs to know about you.

This matters because in distributed systems, you can't reliably share session state across multiple servers. JWT solved that problem by making authentication stateless—the token is self-contained, and any server with the signing key can validate it independently.

The Structure of a JSON Web Token

A JWT isn't just a random string. It's a digitally-signed token that shares information in a specific format: three Base64URL-encoded sections separated by periods. When you see a token like this:

eyJhbGciOiJIUzM4NCJ9.eyJpZCI6IjI5ZDFiZjc5LWU1M2UtNGI5Mi1iYzlkLTk4MDJmMDgzZDVlOCIsImVtYWlsIjoic3R1ZGVudEBqYXZhcHJvLmFjYWRlbXkiLCJmaXJzdE5hbWUiOiJKb2huIiwibGFzdE5hbWUiOiJEb2UiLCJyb2xlcyI6WyJTVUJTQ1JJQkVSIl0sInN1YiI6InN0dWRlbnRAamF2YXByby5hY2FkZW15IiwiaWF0IjoxNzY5MzYxMDg1LCJleHAiOjE3Njk0NDc0ODV9.u8VfKcs4EPy605B5fvMyRMer-8G1dozwUK0VChOFC3GQeH5wMNQ3mCq46sM8Y0vN

You're looking at three distinct parts separated by dots.

The header comes first. It specifies the signing algorithm (HMAC with SHA-384 in this case) and token type. Decode that first section and you get:

{
  "alg": "HS384"
}

The payload follows—the actual claims about the user. This section contains whatever data your application needs: user ID, email, roles, expiration time. Decode the middle section:

{
  "id": "29d1bf79-e53e-4b92-bc9d-9802f083d5e8",
  "email": "student@javapro.academy",
  "firstName": "John",
  "lastName": "Doe",
  "roles": ["SUBSCRIBER"],
  "sub": "student@javapro.academy",
  "iat": 1769361085,
  "exp": 1769447485
}

The signature protects everything. The server takes the header, the payload, and a secret key known only to it, then applies the algorithm specified in the header. That produces the third section—a cryptographic signature that proves the token hasn't been tampered with. Anyone can read the payload (it's just Base64-encoded, not encrypted), but only someone with the secret key can create a valid signature.

This design has consequences. The payload is readable to anyone who has the token, so never put sensitive information like passwords or social security numbers in there. The token's security comes from the signature, not from hiding the data.

You can decode JWTs using tools like jwt.io to inspect their contents. Paste in a token and you'll immediately see the header, payload, and can verify the signature if you have the secret key. This transparency helps with debugging but also reminds you that JWTs aren't encrypted—they're signed.

How JWT Authentication Works in Practice

Authentication with JWT follows a pattern you'll see in most modern web applications. A client sends credentials (username and password), the server validates them, generates a JWT, and returns it to the client. From that point forward, every API request includes the token in the Authorization header, and the server validates the signature before processing the request.

Here's a JWT authentication example showing the complete flow. First, a user registers by sending their information to the registration endpoint:

POST http://localhost:8080/api/auth/register

Request body:

{
  "email": "student@javapro.academy",
  "password": "password123",
  "firstName": "John",
  "lastName": "Doe"
}

user-registration-2026-01-25_12-07-24.png

The server creates the user account and responds with a success message. Registration usually doesn't issue a JWT immediately—you still need to log in. That keeps the authentication flow clean and handles edge cases like email verification.

Response:

{
  "success": true,
  "message": "User registered successfully",
  "data": {
    "id": "29d1bf79-e53e-4b92-bc9d-9802f083d5e8",
    "email": "student@javapro.academy",
    "firstName": "John",
    "lastName": "Doe",
    "roles": ["SUBSCRIBER"]
  },
  "httpStatus": "CREATED"
}

Now the user logs in with their credentials:

POST http://localhost:8080/api/auth/login

Request body:

{
  "email": "student@javapro.academy",
  "password": "password123"
}

user-login-2026-01-25_12-11-31.png

The server validates the password, generates a JWT containing the user's identity and roles, and returns it:

Response:

{
  "success": true,
  "message": "Login successful",
  "data": {
    "jwt": "eyJhbGciOiJIUzM4NCJ9.eyJpZCI6IjI5ZDFiZjc5LWU1M2UtNGI5Mi1iYzlkLTk4MDJmMDgzZDVlOCIsImVtYWlsIjoic3R1ZGVudEBqYXZhcHJvLmFjYWRlbXkiLCJmaXJzdE5hbWUiOiJKb2huIiwibGFzdE5hbWUiOiJEb2UiLCJyb2xlcyI6WyJTVUJTQ1JJQkVSIl0sInN1YiI6InN0dWRlbnRAamF2YXByby5hY2FkZW15IiwiaWF0IjoxNzY5MzYxMDg1LCJleHAiOjE3Njk0NDc0ODV9.u8VfKcs4EPy605B5fvMyRMer-8G1dozwUK0VChOFC3GQeH5wMNQ3mCq46sM8Y0vN"
  },
  "httpStatus": "OK"
}

The client stores this token (typically in localStorage for web apps or secure storage for mobile apps) and includes it in subsequent requests. When accessing protected resources, the client sends the token in the Authorization header using the Bearer scheme:

GET http://localhost:8080/api/users/profile
Authorization: Bearer eyJhbGciOiJIUzM4NCJ9.eyJpZCI6IjI5ZDFiZjc5...

JWT-authentication-2026-01-25_12-17-16.png

The server extracts the token, verifies the signature using its secret key, and if valid, processes the request using the claims from the payload. No database lookup required—the token itself contains everything needed to identify the user and their permissions.

Response:

{
  "success": true,
  "message": "Profile retrieved successfully",
  "data": {
    "id": "29d1bf79-e53e-4b92-bc9d-9802f083d5e8",
    "email": "student@javapro.academy",
    "firstName": "John",
    "lastName": "Doe"
  },
  "httpStatus": "OK"
}

This is the JWT authentication example that demonstrates the complete cycle. The beauty of this approach is that the server doesn't need to maintain any session state. Each request is completely independent, carrying its own proof of authentication.

Understanding JWT Claims

The payload section of a JWT contains claims—statements about the user and additional metadata. Some claims are registered (part of the JWT specification), while others are custom to your application.

Standard registered claims include:

  • sub (subject): Identifies the principal (usually the user's email or username)
  • iat (issued at): Timestamp when the token was created
  • exp (expiration): Timestamp when the token expires
  • iss (issuer): Identifies who issued the token
  • aud (audience): Identifies who the token is intended for

In our example payload, you'll see both standard and custom claims:

JWT-Claims-2026-01-25_12-13-07.png

The expiration claim deserves special attention. Tokens should have a limited lifespan—typically anywhere from 15 minutes to several hours. Once expired, the server rejects the token, and the user must authenticate again. This exp claim provides a security boundary: even if someone steals a token, it becomes useless after expiration.

Some applications implement refresh tokens to handle expiration gracefully. The server issues both a short-lived access token (the JWT) and a long-lived refresh token. When the JWT expires, the client uses the refresh token to get a new JWT without asking the user to log in again. That pattern balances security with user experience.

When JWT Makes Sense (and When It Doesn't)

JWT excels in specific architectural patterns. Microservices architectures benefit enormously because any service can validate a token without coordinating with a central authentication server. Mobile applications prefer JWT because tokens work naturally with REST APIs and don't depend on browser cookies. Single-page applications use JWT to authenticate API requests without managing server-side sessions.

But JWT isn't always the answer. If you're building a traditional server-rendered web application where sessions work perfectly well, adding JWT complexity might not buy you anything. Session-based authentication with secure cookies can be simpler and more appropriate for many use cases.

The stateless nature of JWT creates interesting challenges. You can't revoke a JWT before it expires—the token remains valid until the exp timestamp passes. If a user logs out or you need to invalidate a token immediately (say, after a password change), you need additional infrastructure. Some teams maintain a blacklist of invalidated tokens, but that reintroduces state and undermines JWT's stateless advantage. Others keep token lifetimes very short and rely on refresh tokens for longevity.

Token size matters too. JWTs can get large if you pack too many claims into the payload. Every API request carries the full token, so a 2KB token sent on every request adds up quickly. Session IDs, by contrast, are tiny. The trade-off is clear: JWT moves authentication state from the server to the client, which eliminates database lookups but increases request size.

Security Considerations

Security with JWT depends entirely on keeping the signing secret safe. If an attacker gets your secret key, they can forge tokens with arbitrary claims—impersonating any user, granting themselves admin privileges, setting expiration times far in the future. That secret needs the same level of protection as database credentials or encryption keys.

Never use the "none" algorithm. Some JWT libraries support an "alg": "none" setting that disables signature verification. This was intended for debugging but has been exploited in the wild. Attackers modify the algorithm to "none", remove the signature, and the server blindly trusts the payload. Always validate the algorithm matches what you expect.

The payload is not encrypted. Anyone who intercepts a JWT can decode it and read every claim inside. Use HTTPS for all requests carrying JWTs—otherwise, you're broadcasting user information in plaintext. And never put sensitive data in the payload. Passwords, social security numbers, credit card details—none of that belongs in a JWT.

Token theft is a real threat. If someone steals a valid JWT (through XSS, man-in-the-middle attacks, or other means), they can impersonate the user until the token expires. This is why short expiration times matter. The shorter the window, the less time an attacker has to exploit a stolen token. Some applications also bind tokens to specific IP addresses or user agents, but that creates complications for legitimate users on mobile networks or behind proxies.

Building JWT Authentication Systems

Implementing JWT authentication properly requires careful attention to several moving parts. You need endpoints for registration and login that validate credentials and generate tokens. You need middleware that extracts tokens from request headers, validates signatures, and rejects expired tokens. You need error handling for invalid tokens, expired tokens, and missing tokens. You need to manage the secret key securely and consider key rotation strategies.

The registration endpoint typically hashes passwords before storing them (using bcrypt, Argon2, or similar algorithms designed for password hashing). The login endpoint validates the password against the hash, then generates a JWT with appropriate claims if authentication succeeds. The token generation process involves creating a payload, signing it with the secret key using the specified algorithm, and Base64URL-encoding the result.

Protected endpoints need to validate tokens on every request. The server extracts the token from the Authorization header, verifies the signature matches what you'd get by signing the header and payload with your secret, checks the expiration time hasn't passed, and then extracts claims from the payload to identify the user and their permissions.

Authorization builds on authentication. Once you know who the user is (from the JWT claims), you can check their permissions. The roles array in our example token shows one approach: include role information in the token itself. When the user requests a protected resource, the server checks if their roles include the required permission. Some systems put permissions directly in the token, while others use the user ID from the token to look up permissions in a database. The latter reintroduces a database call but allows for more dynamic permission management.

Spring Boot provides excellent support for JWT authentication through Spring Security. The framework handles much of the complexity: token validation, error handling, integration with authentication and authorization mechanisms. Building a production-ready JWT authentication system involves configuring security filters, implementing JWT utilities for token generation and validation, creating authentication controllers, and securing endpoints with role-based access control.

Summary

JWT stands for JSON Web Token, a compact and self-contained way to securely transmit information between parties as a JSON object. This information is digitally signed using either a secret (with HMAC) or a public/private key pair (with RSA or ECDSA), making JWTs verifiable and trustworthy.

The structure consists of three parts: a header specifying the algorithm and token type, a payload containing claims about the entity and additional metadata, and a signature that proves the token hasn't been tampered with. These parts are Base64URL-encoded and concatenated with periods to form the complete token.

A JWT authentication example follows a predictable pattern: users register and log in with credentials, the server validates those credentials and issues a JWT, clients include that token in subsequent requests via the Authorization header, and the server validates the token before processing each request. This approach works particularly well for distributed systems, microservices, mobile applications, and single-page applications where stateless authentication matters.

The security of JWT depends on protecting the signing secret, always using HTTPS, setting appropriate expiration times, and never including sensitive data in the payload. The payload is readable by anyone who has the token—it's signed, not encrypted.

Understanding JWT lays the foundation for building modern authentication systems. Whether you're developing a REST API, a microservices architecture, or a mobile backend, JWT provides a robust, stateless authentication mechanism that scales with your application. The concepts you've learned here—token structure, authentication flows, security considerations—apply broadly across different technologies and frameworks, making JWT a valuable tool in any developer's skillset.

What is JWT?. Last updated January 25, 2026.


Ready to secure your Spring Boot REST APIs with JWT? Learn to build production-ready authentication systems in our Spring Boot JWT Authentication and Authorization course. Just getting started? Begin with our free Core Java course.

Subscribe and Master Java

If this helped you, you'll love the full Java Program Library4 structured programs with real projects.

$49 /year
Posted in

Get the Java Weekly Digest

Stay sharp with curated Java insights delivered straight to your inbox. Join 5,000+ developers who read our digest to level up their skills.

No spam. Unsubscribe anytime.

Name