ASP.NET Core  

How to Validate JWT Tokens from an Auth Microservice in ASP.NET Core Web API

In modern microservice architectures, it is common to have a dedicated Authentication (Auth) microservice responsible for issuing JWT tokens. Other microservices rely on these tokens to authenticate requests and identify users. In this article, we will learn how to validate a JWT issued by an Auth microservice in another ASP.NET Core Web API, extract CompanyId and UserId from the token, and make it accessible in the service.

Prerequisites

Before we start, ensure you have:

  1. An Auth microservice that issues JWT tokens containing CompanyId and UserId as claims.

  2. A microservice where you want to validate the token.

  3. Basic knowledge of ASP.NET Core middleware and JWT tokens.

Step 1: Setting Up JWT in AppSettings

First, we need to define the secret key or signing key used to validate JWT tokens. Add the following section to your appsettings.json:

{
  "Jwt": {
    "Secret": "YourSuperSecretKeyHere12345" // Must match the Auth service key
  }
}

Tip: Use a strong secret key (minimum 32 characters) and store it securely using Azure Key Vault or AWS Secrets Manager in production.

Step 2: Creating Middleware to Validate JWT Tokens

Middleware is the best way to handle cross-cutting concerns like authentication. We will create a middleware that:

  • Reads the token from the Authorization header.

  • Validates the token signature.

  • Extracts CompanyId and UserId from claims.

  • Stores them in HttpContext.Items for use in controllers.

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

public class TokenValidationMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IConfiguration _configuration;

    public TokenValidationMiddleware(RequestDelegate next, IConfiguration configuration)
    {
        _next = next;
        _configuration = configuration;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Headers.ContainsKey("Authorization"))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Authorization header missing");
            return;
        }

        var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();

        if (string.IsNullOrEmpty(token))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Token missing");
            return;
        }

        try
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"]);

            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false,
                ClockSkew = TimeSpan.Zero
            }, out SecurityToken validatedToken);

            var jwtToken = (JwtSecurityToken)validatedToken;

            // Extract claims
            var companyId = jwtToken.Claims.FirstOrDefault(x => x.Type == "CompanyId")?.Value;
            var userId = jwtToken.Claims.FirstOrDefault(x => x.Type == "UserId")?.Value;

            if (string.IsNullOrEmpty(companyId) || string.IsNullOrEmpty(userId))
            {
                context.Response.StatusCode = 401;
                await context.Response.WriteAsync("Invalid token claims");
                return;
            }

            // Store in HttpContext
            context.Items["CompanyId"] = companyId;
            context.Items["UserId"] = userId;

            await _next(context);
        }
        catch
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Invalid token");
        }
    }
}

Step 3: Register Middleware

In Program.cs or Startup.cs (for ASP.NET Core 6+):

app.UseMiddleware<TokenValidationMiddleware>();

Note: Ensure this middleware is added before UseEndpoints to ensure token validation happens first.

Step 4: Access Claims in Controllers

Once the middleware validates the token and stores claims in HttpContext.Items, you can access them in your controllers:

[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    [HttpGet("my-orders")]
    public IActionResult GetMyOrders()
    {
        var companyId = HttpContext.Items["CompanyId"]?.ToString();
        var userId = HttpContext.Items["UserId"]?.ToString();

        if (string.IsNullOrEmpty(companyId) || string.IsNullOrEmpty(userId))
            return Unauthorized("Invalid token data");

        // Your logic to fetch orders based on companyId and userId
        return Ok(new { CompanyId = companyId, UserId = userId, Orders = new string[] { "Order1", "Order2" } });
    }
}

Step 5: Optional – Remote Validation via Auth Microservice

If you want to ensure the token hasn’t been revoked, you can call the Auth microservice’s Validate Token endpoint:

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

var response = await client.GetAsync("https://authservice/api/validate-token");

if (!response.IsSuccessStatusCode)
{
    context.Response.StatusCode = 401;
    await context.Response.WriteAsync("Token invalid according to Auth service");
    return;
}

This is slower due to network latency but ensures tokens are actively validated.

Best Practices

  1. Use HTTPS for all microservice communication.

  2. Keep secrets secure using environment variables or secret managers.

  3. Validate both issuer and audience if your system has multiple clients or environments.

  4. Use ClaimsPrincipal instead of HttpContext.Items for cleaner integration with User.Claims in large applications.

  5. Cache public keys if using asymmetric signing (RSA) to improve performance.

Conclusion

In microservice architectures, validating JWT tokens from a central Auth service ensures secure communication between services. By using custom middleware, you can:

  • Validate tokens.

  • Extract user-specific claims like CompanyId and UserId.

  • Make them easily accessible in your controllers.

This approach keeps your microservices secure, decoupled, and scalable.