Back to Home

How to Implement JWT Authentication: Step-by-Step Guide

This comprehensive guide explains how to implement JWT authentication for modern web APIs. It covers token architecture, step-by-step implementation with .NET and Node.js examples, security best practices including refresh token rotation and asymmetric signing, and common pitfalls to avoid.

JWT Authentication Implementation: Security Best Practices
Advertisement 728x90

.NET

JWT Authentication: Complete Implementation Guide

Implementing secure authentication is a critical challenge for modern web APIs and applications. JSON Web Tokens (JWTs) have emerged as the industry-standard solution for stateless authentication, allowing servers to verify user identity without maintaining server-side session state . By the end of this guide, you'll understand the complete architecture of JWT-based authentication, be able to implement secure token issuance and validation in your own applications, and have a production-ready strategy for handling token lifecycle management. You'll walk away with the practical knowledge to implement JWT authentication securely in any tech stack.

What You'll Learn

You'll understand how JWTs work architecturally, why proper implementation matters for security, and gain hands-on knowledge for configuring authentication middleware, issuing tokens, and protecting API endpoints. By the end, you'll be equipped to implement JWT authentication in your own projects with confidence in the security decisions you're making.

Google AdInline article slot

Understanding the JWT Authentication Architecture

Before diving into code, it's essential to grasp how JWT authentication works. A JSON Web Token is a compact, URL-safe means of representing claims between two parties . The token consists of three Base64URL-encoded parts separated by dots: header, payload, and signature (header.payload.signature) .

The JWT authentication flow follows a clear pattern: a client authenticates (typically via a login endpoint), receives a signed token, and then includes that token in the Authorization header of subsequent requests. The server validates the token's signature and claims on each request, making the system stateless — no server-side session storage is required .

Key Components of a JWT

Component Purpose Example
Header Specifies signing algorithm and token type {"alg":"RS256","typ":"JWT"}
Payload Contains claims (user data, expiration, issuer) {"sub":"user123","exp":1700000000}
Signature Ensures token integrity and authenticity Cryptographic hash of header+payload with secret

This design makes JWTs ideal for distributed systems and microservices where maintaining shared session state would be impractical .

Google AdInline article slot

How to Implement JWT Authentication: Step-by-Step Guide

The following steps outline a production-ready approach to implementing JWT authentication. While examples use .NET and Node.js, the principles apply across all programming languages.

Step 1: Install the Required Authentication Packages

Modern frameworks provide official JWT Bearer authentication handlers. For .NET, install the Microsoft.AspNetCore.Authentication.JwtBearer package . For Node.js, use jsonwebtoken .

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

# Node.js
npm install jsonwebtoken bcryptjs dotenv

Relying on official, battle-tested libraries is essential — never implement your own cryptographic signing logic .

Google AdInline article slot

Step 2: Configure Authentication Services

The authentication configuration defines how your API validates incoming tokens. Critical validation parameters include :

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,           // Verify token came from known authority
            ValidateAudience = true,         // Verify token is intended for this API
            ValidateLifetime = true,         // Reject expired tokens automatically
            ValidateIssuerSigningKey = true, // Verify cryptographic signature
            ValidIssuer = "https://your-identity-provider.com",
            ValidAudience = "https://your-api.com",
            IssuerSigningKey = publicKey     // The key for signature verification
        };
    });

All four validation flags should be set to true in production. Disabling any of these creates security vulnerabilities . The IssuerSigningKey can be loaded from a PEM file or, in production, automatically retrieved from the identity provider's metadata endpoint .

Step 3: Add Authentication and Authorization Middleware

The order of middleware components is crucial — authentication must run before authorization :

app.UseAuthentication();  // Validates JWT and populates HttpContext.User
app.UseAuthorization();   // Enforces [Authorize] policies on protected endpoints

When a request reaches UseAuthentication, the handler examines the Authorization header, validates the JWT, and creates a ClaimsPrincipal representing the authenticated user. This object is then available throughout the request pipeline for authorization decisions.

Step 4: Protect Endpoints with the [Authorize] Attribute

Securing API endpoints requires the [Authorize] attribute :

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class SecureController : ControllerBase
{
    [HttpGet]
    public IActionResult GetSecureData()
    {
        var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        return Ok($"Authenticated user: {userId}");
    }
}

Requests without a valid token receive a 401 Unauthorized response. When authentication succeeds but lacks required permissions, the API returns 403 Forbidden .

Step 5: Implement Token Issuance (Development Only)

In production, token issuance belongs to a dedicated Identity Provider (IdP) implementing OAuth 2.0 or OpenID Connect . The API's role is limited to validation.

However, for development and testing, you may need a local token endpoint:

// Node.js example with jsonwebtoken
router.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email });
  
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ message: 'Invalid credentials' });
  }
  
  const token = jwt.sign(
    { id: user._id, email: user.email },
    process.env.JWT_SECRET,
    { expiresIn: '15m' } // Short-lived access tokens
  );
  
  res.json({ token });
});

Access tokens should have short lifetimes — typically 15 minutes — to limit the damage from token theft .

Production Security Best Practices

Never Store Tokens in LocalStorage

For web applications, store JWTs in HttpOnly, Secure cookies rather than localStorage or sessionStorage . This prevents cross-site scripting (XSS) attacks from accessing tokens. Cookie configuration should include:

HttpOnly: true       // Prevents JavaScript access
Secure: true         // Only sent over HTTPS
SameSite: Lax or Strict  // CSRF protection

Use Refresh Token Rotation

Since access tokens expire quickly, implement a refresh token mechanism to maintain user sessions without requiring re-authentication :

  • Refresh token: Long-lived (e.g., 7 days), stored securely on the server
  • Access token: Short-lived (e.g., 15 minutes), sent with each request
  • Rotation: Each refresh request issues a new access+refresh pair, invalidating the old refresh token

This approach mitigates the impact of stolen tokens and enables clean logout functionality.

Employ Asymmetric Signing (RS256/ES256) for Production

Use asymmetric algorithms (RS256 or ES256) rather than HMAC (HS256) in production :

Algorithm Key Type Use Case
HS256 Symmetric (shared secret) Single-service applications
RS256/ES256 Asymmetric (public/private key) Multi-service systems, microservices

Asymmetric signing allows multiple services to verify tokens using a public key while only the issuer holds the private key — following the principle of least privilege.

Validate All Standard Claims

The following claims are required for OAuth 2.0 access tokens and must be validated :

  • iss (Issuer) — matches expected authority
  • aud (Audience) — matches your API identifier
  • exp (Expiration) — token not expired
  • sub (Subject) — user identifier
  • iat (Issued At) — token issued in the past
  • jti (JWT ID) — unique identifier for token tracking

Stateless Authentication with Redis

While JWTs are stateless by design, implementing token revocation requires a storage layer. Redis provides an ideal solution :

// Store token metadata for revocation
func (r *Redis) SetJTI(ctx context.Context, key, userID string, exp time.Time) error {
    return r.Client.Set(ctx, key, userID, time.Until(exp)).Err()
}

// Check if token is revoked
func (r *Redis) GetUserByJTI(ctx context.Context, key string) (string, error) {
    return r.Client.Get(ctx, key).Result()
}

Store the token's unique identifier (jti) in Redis with a TTL matching the token's expiration. This enables immediate logout and prevents reuse of compromised tokens .

Common JWT Implementation Mistakes to Avoid

  1. Self-issuing tokens in production: APIs should validate tokens, not issue them. Use a dedicated IdP for issuance .

  2. Weak signing keys: Use cryptographically strong secrets (at least 32 bytes) generated via openssl rand -base64 32 .

  3. Disabling claim validation: Never set ValidateIssuer or ValidateAudience to false in production .

  4. Long token lifetimes: Access tokens should expire within 15 minutes. Longer lifetimes increase attack surface .

  5. No revocation mechanism: Implement logout and token revocation using Redis or a similar store .

Frequently Asked Questions

What is the difference between JWT authentication and OAuth 2.0? JWT is a token format, while OAuth 2.0 is an authorization framework. OAuth 2.0 defines how to obtain tokens (including JWTs), while JWT defines the structure of the token itself. They are complementary — OAuth 2.0 can use JWTs as access tokens, but you can also use JWTs independently for authentication in smaller systems.

How do I securely store JWTs on the client side? Store JWTs in HttpOnly, Secure cookies rather than localStorage or sessionStorage. HttpOnly cookies prevent JavaScript access, protecting against XSS attacks. For mobile applications, use secure platform-specific storage mechanisms.

Can I revoke a JWT before it expires? Yes, by maintaining a revocation list or store of token identifiers (jti claims). Redis is ideal for this — store the jti with a TTL matching the token's expiration. For each request, check if the jti exists in the revocation store. This enables immediate logout and token invalidation.

Should I use HS256 or RS256 for signing JWTs? Use RS256 (asymmetric) for production multi-service environments where token issuance and validation occur on different systems. Use HS256 only for single-service applications where the same secret can be securely shared. Asymmetric signing allows validation services to use only the public key, following the principle of least privilege.

How do I implement refresh tokens securely? Issue a long-lived refresh token (7 days) alongside the short-lived access token (15 minutes). Provide a /refresh endpoint that accepts the refresh token, validates it, and returns a new access+refresh pair. Rotate refresh tokens on each use to prevent token replay attacks. Store refresh tokens securely (e.g., in Redis) and invalidate them on logout .

Sources

  • Duende Software. "A step by step JWT authentication example." Duende Software. 2025.
  • Microsoft Learn. "Configure JWT bearer authentication in ASP.NET Core." Microsoft. 2025.
  • Duende Software. "Implementing Token Authentication in Controller-Based ASP.NET Core Web APIs." Duende Software. 2026.
  • QuickNode. "How to Implement JSON Web Tokens (JWT) Authorization." QuickNode. 2025.
  • freeCodeCamp. "How to Build a Secure Authentication System with JWT and Refresh Tokens." freeCodeCamp. 2025.
  • Vonage. "JWT Authentication in Go with Gin." Vonage Developer. 2025.

— Editorial Team

Advertisement 728x90

Read Next