Development

JSON Web Tokens (JWT): A Complete In-Depth Beginners Tutorial

JWT represents more than just another authentication mechanism—it’s a paradigm shift toward stateless, scalable, and secure authentication that eliminates the bottlenecks of server-side session management. This comprehensive beginners-friendly yet very in-depth tutorial on JWT will equip you with the complete understanding needed to implement, secure, and optimize JWT authentication systems. You’ll master the technical foundations, explore real-world implementation strategies, discover critical security best practices, and learn advanced techniques that distinguish professional-grade authentication systems from basic implementations.

Introduction to JWT

JSON Web Token (JWT, often pronounced as “jot”) is an open standard (RFC 7519) that defines a compact, self-contained way to transmit information between parties as a JSON object securely. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

But why should you care about JWT? Well, in today’s world of microservices, distributed systems, and stateless applications, traditional session-based authentication falls short. JWT solves many of these problems with its stateless nature and ability to carry user information directly in the token.

As an illustration, think of JWT as a digital passport. Just as a passport contains information about you and has security features to prevent forgery, a JWT contains information about a user and provides cryptographic signature capability to ensure its authenticity.

Before we dive deeper, let’s understand what makes JWT special:

  • Compact: JWTs are small and that you can send through URL, POST parameter, or inside an HTTP header, making them fast to transmit.
  • Self-contained: The token itself contains all the necessary information about the user, eliminating the need to query the database multiple times.
  • Secure: You can digitally sign JWTs, ensuring nobody can temper with the information.

Now that we have a basic understanding, let’s explore the structure of a JWT to see what makes it tick.

🩻 JWT Structure and Components:

When I first looked at a JWT string, it appeared as a random jumble of characters. But there’s a method to this madness. A JWT consists of three parts separated by dots (.):

  1. Header
  2. Payload
  3. Signature

Fig: JWT Token Example with segments

Let’s break down each part:

#1 Header

Firstly, The header typically have two parts: the type of token (“JWT”) and the signing algorithm being used, such as HMAC SHA256 or RSA.

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

This JSON is then Base64Url encoded to form the first part of the JWT.

#2 Payload

Secondly, The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. There are three types of claims:

  • Registered claims: Predefined claims like iss (issuer), exp (expiration time), sub (subject), and aud (audience).
  • Public claims: These can be defined at will by those using JWTs.
  • Private claims: Custom claims created to share information between parties.

Here’s an example payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022,
  "exp": 1516242622
}
JSON

This JSON is then Base64Url encoded to form the second part of the JWT.

#3 Signature

For the third and final part, we got the signature. To create the signature portion, we need to take the encoded header, the encoded payload, a secret, and the algorithm specified in the header and sign that.

For example, if you want to use the HMAC SHA256 algorithm, the signature will:

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

The signature is used to verify that the message wasn’t changed along the way. In the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.

All three parts are combined, resulting in a token that can be passed through URL parameters, HTTP headers, or request bodies. Feel free to use our JWT Builder dev tool to create an example token of your own.

Overall, I like to think of the JWT structure as a sealed envelope. The header is like the type of envelope, the payload is the message inside, and the signature is the wax seal that ensures the envelope hasn’t been tampered with.

How big can a JWT be?

While there’s no inherent size limit, JWTs are passed with each request, often in headers which may have size limitations. As a best practice, keep JWTs under 8KB, and preferably much smaller. If you need to include more data, consider using a reference to server-side stored data instead.

Now that we know about the structure let’s find out how JSON Web Tokens work in practice.

❓How JWT Authentication Works

Fig: JSON Web Token Workflow diagram

Now that we understand the structure let’s see how JWT fits into authentication workflows. The process is surprisingly straightforward:

  1. User logs in: The user provides the server’s credentials (username/password).
  2. The server generates a token: Once it verifies the credentials, it generates a JWT containing user information and permissions.
  3. The server sends the token: The JWT is returned to the client.
  4. The client stores the token, Usually in local storage or a cookie.
  5. The client sends the token with subsequent requests. For protected routes, the client includes the JWT in the Authorization header.
  6. The server validates the token: The server checks the signature and claims to authenticate the request.
  7. The server responds with data: If the token is valid, the server processes the request and sends back the appropriate data.

The beauty of this flow is its statelessness. The server doesn’t need to keep track of active sessions or query the database for user information on every request. All the necessary data is right there in the token!

Let me share a real-world analogy that helped me understand this better. JWT authentication is like entering a VIP area at an event:

  1. You show your ID at the main entrance (login)
  2. The staff gives you a wristband with your name and access level printed on it (JWT)
  3. As you move through different areas, security needs to check your wristband (token validation)
  4. They don’t need to call the main entrance each time to verify you (no session storage)

This approach is particularly valuable in distributed systems where the authentication server might be separate from the resource servers.

Now that we understand the workflow, let’s implement an example application to try it out!

🛠️ Implementing JSON Web Tokens:

Theory is great, but let’s get our hands dirty with some code. I’ll show you how to implement JWT authentication in a simple Node.js application using Express and the jsonwebtoken Library.

First, install the necessary packages:

npm install express jsonwebtoken bcrypt
Bash

Now, let’s create a basic server with login and protected routes:

const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

const app = express();
app.use(express.json());

// This would be your database in a real application
const users = [
  {
    id: 1,
    username: 'john',
    // Hashed password for 'password123'
    password: '$2b$10$A7gQHBIASUhr58rusrWy0ucLQUv10GjZBXdqInCrivVVI06FU9JUq',
  }
];

const SECRET_KEY = 'your-secret-key'; // In production, use environment variables

// Login route
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  
  // Find user
  const user = users.find(u => u.username === username);
  if (!user) {
    return res.status(401).json({ message: 'Invalid credentials: User not found' });
  }
  
  // Check password
  const isPasswordValid = await bcrypt.compare(password, user.password);
  if (!isPasswordValid) {
    return res.status(401).json({ message: 'Invalid credentials: password invalid' });
  }
  
  // Generate token
  const token = jwt.sign(
    { id: user.id, username: user.username },
    SECRET_KEY,
    { expiresIn: '1h' }
  );
  
  res.json({ token });
});

// Middleware to authenticate token
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // "Bearer TOKEN"
  
  if (!token) {
    return res.status(401).json({ message: 'Authentication required' });
  }
  
  jwt.verify(token, SECRET_KEY, (err, user) => {
    if (err) {
      return res.status(403).json({ message: 'Invalid or expired token' });
    }
    
    req.user = user;
    next();
  });
}

// Protected route
app.get('/profile', authenticateToken, (req, res) => {
  res.json({ message: `Welcome, ${req.user.username}!` });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
JavaScript

This simple implementation covers the core workflow:

  1. A user logs in with credentials
  2. The server validates them and generates a JWT
  3. The client receives the token and can use it for protected routes
  4. The server verifies the token before granting access

Steps to Use this Test Server:

  • Run the server node index.js
  • Make a login request: curl -X POST http://localhost:3000/login -H "Content-Type: application/json" -d '{"username": "john", "password": "password123"}'
  • Now make a test request to /profile endpoint to verify if the token authentication is working: curl -X GET http://localhost:3000/profile -H "Authorization: Bearer YOUR_TOKEN_HERE"
  • To understand what’s inside the token, use the JWT Decoder tool to debug and examine.

How should I store JWTs on the client side?

For web applications, you have several options: HttpOnly cookies (Most secure but require proper CSRF protection), localStorage (Convenient but vulnerable to XSS attacks) and JavaScript variables(Secure against XSS but lost on page refresh). The best approach depends on your security requirements and application architecture.

You’d want to add more sophisticated error handling, refresh token logic, and proper storage mechanisms for a real-world application, but this gives you the basic idea. With that in mind, let’s explore some security best practices to ensure our implementation is robust.

🔐JWT Security Best Practices

As with any security mechanism, JWT implementation requires careful attention to avoid common pitfalls. Here are some best practices:

1. Protect Your Secret Keys

  • Use strong, randomly generated keys (minimum 256 bits)
  • Store keys in environment variables, never in code
  • Rotate keys periodically
  • Consider asymmetric keys (public/private) for enhanced security

2. Set Appropriate Expiration Times

  • Use the exp claim with short lifetimes (15-60 minutes for web apps)
  • Implement refresh token patterns for longer sessions
  • Adjust expiration based on resource sensitivity

3. Keep Payloads Small

  • Include only necessary information
  • Avoid sensitive data (encoded ≠ encrypted)
  • Use identifiers for frequently changing data instead of storing it directly

4. Always Use HTTPS

  • Mandatory in production to prevent token interception
  • Consider secure and HttpOnly cookies for web applications

5. Handle Token Revocation

  • Maintain blacklists for revoked tokens when needed
  • Use short expiration times to minimize exposure
  • Implement token rotation for sensitive operations

6. Validate All Claims

  • Consider client identifier validation for mobile/IoT apps
  • Verify expiration (exp)
  • Check issuer (iss) in multi-system environments
  • Validate audience (aud) to ensure proper token usage

Now that we have a good foundation on implementation and best practices, let’s expand our knowledge with real-world use cases.

Pro Tip💡: In addition to this JWT starter tutorial, we have an comprehensive best practices guide, that you can use to learn about JWT Best Practices more in-depth.

👀 Common JWT Use Cases

JWT’s versatility makes it suitable for many scenarios beyond simple authentication. Here are some everyday use cases I’ve implemented:

Single Sign-On (SSO)

JWT excels in SSO scenarios where a user needs access to multiple related applications:

  • A user logs in once to the authentication server
  • The auth server issues a JWT
  • The token is used across multiple services or applications
  • Each service independently validates the token

This approach simplifies user experience and centralizes authentication logic.

Information Exchange

JWTs can securely transmit information between parties:

  • The sender creates a JWT containing the required data
  • The recipient verifies the token to ensure data integrity
  • This works well for sharing data between trusted services

Authorization with JSON Web Tokens

Beyond authentication, JWTs excel at authorization:

  • Include user permissions or roles in the token payload
  • Services can make authorization decisions without additional database lookups
  • Example: { "user_id": 123, "role": "admin", "permissions": ["read", "write", "delete"] }
  • ⚠️ Caution: Be careful about too granular permissions, though; the token may become too big to fit in the header and fail the request.

Client-Side Sessions

For applications requiring persistence across page reloads:

  • Store non-sensitive session data in the JWT
  • Keep the JWT in localStorage or a cookie
  • Eliminate server-side session storage completely

API Authentication

For RESTful or GraphQL APIs:

  • Use JWTs to authenticate API clients
  • Include rate-limiting information or subscription tiers
  • Validate tokens at the API gateway level

To conclude, each use case leverages JWT’s core strengths: compactness, self-containment, and security.

Additional Reference 📖 : Learn more with a real-world JWT Case Study .

💡JWT vs. Other Authentication Methods

Let’s compare JWT with these alternatives to understand where it shines and where it might not be the best choice.

Criteria / ConcernJSON Web TokensSession-BasedBasic AuthenticationOAuth 2.0SAML
Storage / State ManagementStateless – doesn’t require server storageThe server maintains a session state; the session ID is stored in a cookieStateless – sends credentials with every requestUses access tokens (typically stateless for validation)Stateless token exchange; not dependent on server-side sessions
Token / Credential ContentSelf-contained token carrying all necessary informationThe cookie holds only a session ID; actual session data is kept on the serverSends username and password with each requestIssues access (and refresh) tokens that represent delegated authorizationXML-based tokens carrying comprehensive identity information
Implementation ComplexitySimpler for first-party applications; easy integrationStraightforward in monolithic applicationsVery simple implementation but has minimal securityMore complex flows involving multiple token types and delegated authorizationComplex due to XML format and integration with enterprise SSO system
Security FeaturesBuilt-in expiration and additional security controls (if well implemented)The server validates the session ID against stored dataLacks built-in expiration; overall, less secureEnhanced security through delegated authorization and specialized token typesProvides robust, comprehensive identity information suitable for enterprise environments
Use Case / SuitabilityIdeal for distributed systems, microservices, modern web/mobile appsBest for monolithic applicationsSuitable for minimal or simple scenarios but not for high-security needsBest for third-party integrations and scenarios requiring delegated authorizationOptimal for enterprise SSO where existing SAML infrastructure is in place

In summary, while JWT might be a good fit compared to others in many scenarios, it also might not be ideal for the following scenarios:

  • Applications requiring immediate token revocation
  • You need complete encryption of payload data
  • Simple websites with server-side rendering
  • Scenarios where tokens might grow very large

🚨Troubleshooting JWT Issues

Even though you might have a solid understanding, JWT implementations can sometimes go wrong. Here are some common issues I’ve encountered and how to fix them:

Invalid Signature Errors

Symptoms: Authentication fails with signature verification errors

Potential Causes:

  • Mismatched secret keys between token creation and verification
  • Using different algorithms for signing and verifying
  • Token tampering

Solutions:

  • Make sure to use the same secret key consistently
  • Verify that the signing algorithm matches
  • Check for any token manipulation in transit

Token Expiration Problems

Symptoms: Users get logged out unexpectedly, or tokens don’t expire when they should

Potential Causes:

  • Clock skew between servers
  • Incorrect expiration time calculation
  • Missing validation of the exp Claim

Solutions:

  • Synchronize server clocks using NTP
  • Add a small grace period for expiration (e.g., 30 seconds)
  • Ensure you’re validating the expiration time

CORS Issues with JWTs

Symptoms: Authentication works in Postman but fails in browsers

Potential Causes:

  • Missing CORS headers for JWT-related endpoints
  • Issues with how the token is being sent in browser requests

Solutions:

  • Make sure to set CORS headers properly authentication endpoints
  • Verify that tokens are being sent in the Authorization header
  • Check the browser console for any CORS-related errors

Payload Size Limitations

Symptoms: Authentication fails with very large tokens

Potential Causes:

  • Including too much data in the JWT payload
  • Exceeding URL length limits if tokens are passed in URLs

Solutions:

  • Minimize data stored in tokens
  • Use token identifiers with server-side storage for large user data
  • Don’t pass tokens in URL parameters

Secure Storage Concerns

Symptoms: Someone stole the Token or reused it maliciously

Potential Causes:

  • Insecure storage of tokens on the client
  • XSS vulnerabilities exposing tokens
  • Missing HTTPOnly or Secure flags on cookies

Solutions:

  • For browser clients, consider HttpOnly cookies
  • Implement proper XSS protections
  • For SPAs, use techniques like token renewal to mitigate XSS risks

By anticipating these common issues, you can save yourself hours of troubleshooting and build more robust JWT implementations.

👽Advanced JWT Techniques

Once you’re comfortable with basic JSON Web Tokens implementation, you can explore more advanced techniques to enhance your authentication system:

Refresh Token Patterns

Instead of extending JWT expiration times (which creates security risks), implement a refresh token pattern:

  1. Issue two tokens at login: a short-lived access token (JWT) and a longer-lived refresh token
  2. When the access token expires, use the refresh token to obtain a new one
  3. Store refresh tokens securely (database) with user associations
  4. Allow refresh tokens to be revoked if necessary

This approach combines the benefits of JWTs with the ability to revoke access when needed.

Token Rotation

For high-security applications, implement token rotation:

  1. Each time a token is used, generate a new one
  2. Return the new token in the response
  3. Invalidate the previous token
  4. This limits the damage if a token is stolen

Implementing Symmetric vs. Asymmetric Signing

  • Symmetric signing (HMAC): Same secret key for signing and verification
    • Simpler to implement
    • All services need the same secret
    • Better for internal services
  • Asymmetric signing (RSA/ECDSA): Public/private key pairs
    • Only the authentication server needs the private key
    • Services only need the public key to verify
    • It is better for distributed systems with varying trust levels

Fine-Grained Authorization

Although JWT provides great role-based authorization, we can go beyond that:

  1. Include detailed permission claims in your JWTs
  2. Implement a claims-based authorization system
  3. Use nested or hierarchical permissions if necessary

Here’s a quick example of a JWT payload with granular permissions:

{
  "sub": "user123",
  "permissions": {
    "articles": ["read", "create"],
    "comments": ["read", "create", "update", "delete"],
    "users": ["read"]
  },
  "exp": 1516242622
}
JSON

Monitoring and Analytics

Generally, Implement logging and monitoring for your JWT system:

  1. Log failed authentication attempts
  2. Monitor token usage patterns
  3. Set up alerts for unusual activity
  4. Collect metrics on token creation, validation, and expiration

As your application grows, these advanced techniques will help you build a more robust, secure, and maintainable JWT authentication system.

📖 FAQs About JSON Web Tokens

Can JWTs be used for authorization, not just authentication?

Absolutely! In fact, JWTs excel at authorization. You can make authorization decisions without additional database lookups by including permission claims in the token payload. This makes JWTs ideal for microservices architectures where each service needs to make independent authorization decisions.

Are JWTs encrypted?

By default, no. Standard JWTs are signed but not encrypted, meaning anyone can read the payload by decoding the Base64Url encoding. If you need to include sensitive information, you should either not put sensitive data in JWTs, or use JWE (JSON Web Encryption) to encrypt the token.

Can I revoke a JWT before it expires?

So, JWTs are designed to be stateless, which means there’s no built-in revocation mechanism. However, you can implement revocation through short expiration times, a blacklist of revoked tokens (though this adds state) and/or a version number in the JWT that’s checked against the user’s current token version in the database

What happens if my JWT secret key is compromised?

If your secret key is compromised, an attacker could create valid tokens for any user. In this scenario, you should immediately rotate your secret key, force all users to log in again and consider implementing a system that can quickly change keys without disrupting service

Conclusion

In summary, JSON Web Tokens solve many of the challenges we face in today’s complex application environments:

  • They eliminate the need for session storage
  • They work seamlessly across different domains and services
  • They provide a standardized way to represent user identity and permissions
  • They scale horizontally without a shared state

However, like any technology, JWT isn’t a silver bullet. It requires careful implementation, proper security practices, and an understanding of its limitations. The most successful authentication systems often combine JWT with complementary approaches like refresh tokens to get the best of both worlds.

While you continue your journey with JWT, remember that security always evolves. Stay updated on best practices, be aware of new vulnerabilities, and be ready to adapt your implementation as needed.

I hope this beginner-friendly JWT Tutorial helped you with the fundamentals you need to jump-start your auth journey. I’d love to hear about your experiences implementing JWT in your own projects. What challenges did you face? What solutions did you discover? Happy building 🛠️!

Rana Ahsan

Rana Ahsan is a seasoned software engineer and technology leader specialized in distributed systems and software architecture. With a Master’s in Software Engineering from Concordia University, his experience spans leading scalable architecture at Coursera and TopHat, contributing to open-source projects. This blog, CodeSamplez.com, showcases his passion for sharing practical insights on programming and distributed systems concepts and help educate others. Github | X | LinkedIn

Recent Posts

Advanced Service Worker Features: Push Beyond the Basics

Unlock the full potential of service workers with advanced features like push notifications, background sync, and performance optimization techniques that transform your web app into…

4 days ago

Service Workers in React: Framework Integration Guide

Learn how to integrate service workers in React, Next.js, Vue, and Angular with practical code examples and production-ready implementations for modern web applications.

2 weeks ago

Service Worker Caching Strategies: Performance & Offline Apps

Master the essential service worker caching strategies that transform web performance. Learn Cache-First, Network-First, and Stale-While-Revalidate patterns with practical examples that'll make your apps blazingly…

3 weeks ago

This website uses cookies.