Web API  

How to Secure REST APIs Using JWT Authentication in .NET?

Securing REST APIs is a critical requirement in modern distributed systems, microservices architectures, and cloud-native applications. JWT (JSON Web Token) authentication is one of the most widely used mechanisms for securing ASP.NET Core Web APIs because it enables stateless, scalable, and secure authentication across clients and services.

This article provides a complete implementation guide for JWT authentication in .NET, including token generation, configuration, validation, role-based authorization integration, security best practices, and real-world architectural considerations.

What Is JWT Authentication?

JWT (JSON Web Token) is a compact, URL-safe token format used to securely transmit claims between two parties. It consists of three parts:

  • Header

  • Payload (claims)

  • Signature

Structure:

Header.Payload.Signature

The token is digitally signed using a secret key (HMAC) or public/private key pair (RSA), ensuring integrity and authenticity.

Why Use JWT for REST APIs?

JWT authentication is preferred for REST APIs because:

  • It is stateless (no server-side session storage required)

  • It scales well in distributed environments

  • It works across domains and microservices

  • It integrates easily with mobile and SPA clients

  • It supports role-based and claims-based authorization

Unlike cookie-based authentication, JWT is ideal for APIs consumed by external clients.

How JWT Authentication Works

  1. User logs in with credentials.

  2. Server validates credentials.

  3. Server generates a JWT token.

  4. Client stores the token (usually in memory or secure storage).

  5. Client sends token in Authorization header.

  6. Server validates token for each request.

Authorization header format:

Authorization: Bearer {token}

Step 1: Install Required Packages

Install JWT authentication package:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Step 2: Configure JWT Authentication in Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

var jwtKey = builder.Configuration["Jwt:Key"];
var issuer = builder.Configuration["Jwt:Issuer"];

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = issuer,
        ValidAudience = issuer,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

Step 3: Add JWT Configuration in appsettings.json

{
  "Jwt": {
    "Key": "YourSuperSecretKeyHere",
    "Issuer": "YourAppIssuer"
  }
}

Always store secrets securely using environment variables or secret managers in production.

Step 4: Generate JWT Token

Create a login endpoint that generates a token after validating credentials.

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using System.Text;

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly IConfiguration _configuration;

    public AuthController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpPost("login")]
    public IActionResult Login(LoginModel model)
    {
        if (model.Username != "admin" || model.Password != "password")
            return Unauthorized();

        var claims = new[]
        {
            new Claim(ClaimTypes.Name, model.Username),
            new Claim(ClaimTypes.Role, "Admin")
        };

        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));

        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Issuer"],
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(30),
            signingCredentials: creds);

        return Ok(new
        {
            token = new JwtSecurityTokenHandler().WriteToken(token)
        });
    }
}

public class LoginModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}

In real applications, credentials must be validated against a database with hashed passwords.

Step 5: Protect API Endpoints

Use the Authorize attribute to secure controllers.

[Authorize]
[ApiController]
[Route("api/secure")]
public class SecureController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok("This endpoint is protected.");
    }
}

Role-based protection:

[Authorize(Roles = "Admin")]

JWT vs Cookie-Based Authentication

ParameterJWT AuthenticationCookie Authentication
State ManagementStatelessStateful
ScalabilityHighLimited
API SuitabilityExcellentNot ideal
Mobile SupportStrongWeak
Server StorageNot requiredRequired
Cross-Domain SupportEasyComplex

JWT is more suitable for REST APIs and distributed systems.

Token Expiration and Refresh Tokens

Best practice:

  • Short-lived access token (15–30 minutes)

  • Long-lived refresh token stored securely

Refresh token flow:

  1. Access token expires.

  2. Client sends refresh token.

  3. Server validates refresh token.

  4. New access token is issued.

Never store refresh tokens in local storage without protection.

Security Best Practices

  • Use HTTPS only

  • Store secrets securely

  • Use strong signing keys

  • Validate issuer and audience

  • Implement token expiration

  • Avoid storing sensitive data in payload

  • Implement refresh token mechanism

  • Revoke tokens on logout

  • Apply rate limiting on login endpoint

Common Mistakes

  • Hardcoding secret keys

  • Using long-lived tokens without expiration

  • Not validating issuer or audience

  • Storing tokens in insecure browser storage

  • Not hashing passwords

Proper configuration prevents security vulnerabilities.

Real-World Production Scenario

In a microservices architecture:

  • Authentication service issues JWT tokens.

  • API Gateway validates tokens.

  • Downstream services trust validated claims.

  • Role-based access is enforced per service.

This enables centralized authentication with decentralized authorization.

Summary

Securing REST APIs using JWT authentication in .NET involves configuring the JwtBearer middleware, generating signed tokens after credential validation, protecting endpoints using the Authorize attribute, and validating tokens for each request. JWT provides a stateless, scalable authentication mechanism suitable for distributed systems, microservices, and cloud-native applications. By implementing strong signing keys, token expiration policies, HTTPS enforcement, refresh token strategies, and role-based authorization, developers can build secure and production-ready ASP.NET Core APIs.