ASP.NET Core  

Microservices Security in ASP.NET Core

Introduction

Nowadays, many companies are moving from monolithic applications to microservices because microservices are scalable, easier to maintain, and allow each service to be deployed independently.

However, adopting microservices introduces new challenges. One of the most critical challenges is security.

In monolithic applications, security is simpler because there is a single application with one entry point. In microservices, multiple services expose APIs and communicate with each other over the network, making security more complex.

This article explains how to implement security in a microservices architecture using:

  • JWT (JSON Web Tokens)

  • API Gateway

  • YARP (Yet Another Reverse Proxy) in ASP.NET Core

Why Security Is More Complex in Microservices

In monolithic systems, there is typically:

  • One application

  • One entry point

  • One authentication middleware

  • One deployment

In microservices, however:

  • Many services run independently

  • Each service exposes its own API endpoints

  • Services communicate over the network

  • Services scale dynamically

  • IP addresses and ports change frequently

As a result, the system has multiple entry points, making security harder to manage.

Main Goals of Microservices Security

The primary goals of securing microservices are:

  • Correctly authenticate users

  • Authorize users based on roles or permissions

  • Ensure only trusted systems can call internal services

  • Prevent direct access to microservices

  • Secure service-to-service communication

  • Protect tokens and sensitive data

What Is JWT and How It Works

A JSON Web Token (JWT) is a secure method for transmitting information between a client and a server. It is commonly used in APIs to authenticate users and prevent unauthorized access.

A JWT consists of JSON data that is digitally signed, ensuring the token cannot be altered.

JWT Structure

A JWT has three parts:

1. Header

Contains metadata about the token, such as:

  • Token type (JWT)

  • Signing algorithm (HMAC, RSA, ECDSA, etc.)

2. Payload

Contains claims (user-related information), such as:

  • Username

  • Role

  • User ID

3. Signature

Ensures token authenticity by verifying:

  • The token was issued by a trusted authority

  • The payload has not been modified

JWT Format

xxxxx.yyyyy.zzzzz

Where:

  • xxxxx – Header

  • yyyyy – Payload

  • zzzzz – Signature

Why JWT Is Used in Microservices

JWT is well suited for microservices because it:

  • Is stateless (no server-side sessions)

  • Includes authentication data in each request

  • Works well in distributed environments

  • Supports claims and roles

  • Is easy to validate

Where Authentication Should Happen

There are two common approaches to authentication in microservices.

Approach 1: Each Microservice Handles Authentication

In this approach:

  • Every service validates the JWT token

  • Every service enforces role and permission checks

Pros

  • Independent security per service

  • Works even if the gateway is removed

Cons

  • Duplicate security logic across services

  • Harder to manage policies

  • Increased configuration and development effort

Approach 2: API Gateway Handles Authentication (Recommended)

In this approach:

  • The client sends the JWT token to the API Gateway

  • The gateway validates the token

  • Requests are forwarded to internal services

  • Microservices remain private

Pros

  • Centralized security configuration

  • Cleaner microservices

  • Easier implementation of cross-cutting concerns

Cons

  • Gateway becomes a critical component

  • Internal traffic still requires protection

Recommended Architecture: JWT + API Gateway

The request flow is:

  • Client authenticates with AuthService and receives a JWT

  • Client sends requests to the API Gateway with the JWT

  • Gateway validates the token

  • Gateway routes the request to the target microservice

  • Response flows back through the gateway

Implementation Overview

The example uses three projects:

  • AuthService – Generates JWT tokens

  • ApiGateway – Uses YARP for routing and JWT validation

  • OrderService – Internal service protected behind the gateway

Step 1: AuthService (JWT Generation)

Responsibilities

  • Validate user credentials

  • Generate JWT tokens

  • Return tokens to clients

Required Package

dotnet add package System.IdentityModel.Tokens.Jwt

appsettings.json

{
  "Jwt": {
    "Key": "THIS_IS_A_DEMO_SECRET_KEY_CHANGE_IT",
    "Issuer": "MicroservicesAuthServer",
    "Audience": "MicroservicesClients",
    "ExpireMinutes": 60
  }
}

TokenService

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

public class TokenService
{
    private readonly IConfiguration _config;

    public TokenService(IConfiguration config)
    {
        _config = config;
    }

    public string GenerateToken(string username)
    {
        var jwt = _config.GetSection("Jwt");
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwt["Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, username),
            new Claim(ClaimTypes.Role, "User")
        };

        var token = new JwtSecurityToken(
            issuer: jwt["Issuer"],
            audience: jwt["Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(Convert.ToDouble(jwt["ExpireMinutes"])),
            signingCredentials: creds
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

AuthController

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
    private readonly TokenService _tokenService;

    public AuthController(TokenService tokenService)
    {
        _tokenService = tokenService;
    }

    [HttpPost("login")]
    public IActionResult Login([FromBody] LoginRequest request)
    {
        if (request.Username == "admin" && request.Password == "123")
        {
            var token = _tokenService.GenerateToken(request.Username);
            return Ok(new { token });
        }

        return Unauthorized("Invalid username or password");
    }
}

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

Program Configuration

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<TokenService>();

var app = builder.Build();

app.MapControllers();
app.Run();

AuthService runs on:

https://localhost:5005

Step 2: OrderService (Internal Microservice)

OrderService exposes protected APIs and is not accessible publicly.

using Microsoft.AspNetCore.Mvc;

namespace OrderService.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class OrderController : ControllerBase
    {
        [HttpGet("{id}")]
        public IActionResult GetOrder(int id)
        {
            return Ok(new
            {
                OrderId = id,
                Status = "Created",
                Amount = 5000
            });
        }
    }
}

OrderService runs on:

https://localhost:5001

Step 3: API Gateway Using YARP

Responsibilities

  • Validate JWT tokens

  • Enforce authorization policies

  • Route requests to microservices

Required Packages

dotnet add package Yarp.ReverseProxy
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Gateway appsettings.json

{
  "Jwt": {
    "Key": "THIS_IS_A_DEMO_SECRET_KEY_CHANGE_IT",
    "Issuer": "MicroservicesAuthServer",
    "Audience": "MicroservicesClients"
  },
  "ReverseProxy": {
    "Routes": {
      "orderRoute": {
        "ClusterId": "orderCluster",
        "Match": {
          "Path": "/orders/{**catch-all}"
        },
        "AuthorizationPolicy": "JwtPolicy"
      }
    },
    "Clusters": {
      "orderCluster": {
        "Destinations": {
          "d1": {
            "Address": "https://localhost:5001/"
          }
        }
      }
    }
  }
}

Gateway Program Configuration

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

var builder = WebApplication.CreateBuilder(args);

var jwt = builder.Configuration.GetSection("Jwt");
var key = Encoding.UTF8.GetBytes(jwt["Key"]);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            ValidateLifetime = true,
            ValidIssuer = jwt["Issuer"],
            ValidAudience = jwt["Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ClockSkew = TimeSpan.Zero
        };
    });

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("JwtPolicy", policy =>
    {
        policy.RequireAuthenticatedUser();
    });
});

builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();

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

app.MapReverseProxy();

app.Run();

API Gateway runs on:

https://localhost:7000

Step 4: Testing the Complete Flow

1. Generate JWT Token

POST https://localhost:5005/api/auth/login
{
  "username": "admin",
  "password": "123"
}

2. Call Order API via Gateway

GET https://localhost:7000/orders/api/order/1
Authorization: Bearer <TOKEN>

3. Call Without Token

Requests without a token will return:

401 Unauthorized

Preventing Direct Access to Microservices

To prevent direct access to internal services:

  • Deploy services inside a private network

  • Expose only the API Gateway publicly

  • Use firewall rules or security groups

  • In Kubernetes, use ClusterIP services

Additional Security Best Practices

  • Use HTTPS everywhere

  • Implement refresh tokens

  • Apply role-based authorization

  • Add rate limiting at the gateway

  • Enable centralized logging and monitoring

Conclusion

Microservices security goes beyond token generation. A robust approach includes:

  • Issuing tokens from an authentication service

  • Validating tokens at the API Gateway

  • Routing requests only after validation

  • Keeping microservices private

  • Securing internal communication

Using YARP in ASP.NET Core provides a clean and production-ready approach to implementing API Gateway security.