Skip to main content

๐Ÿš€Master User Authentication in .NET 8 Web API Email Confirmation, Password Reset, 2FA & Lockout with JWT & Identity๐Ÿ”

๐Ÿ”’ How to Secure Your Microservices with JWT Authentication & Ocelot API Gateway? ๐Ÿค”๐Ÿ”‘ Find Out Now

How Secure Are Your Microservices?๐Ÿ”’ Add JWT Authentication to Ocelot API Gateway Today!๐Ÿš€

Introduction

We're diving into an essential aspect of securing our microservices architecture: adding JWT authentication to our microservices that use Ocelot as an API gateway. If you've been following along, you know we've already created our microservices and set up Ocelot to redirect all API calls seamlessly. Now, it's time to add an extra layer of security.

Imagine a scenario where your microservices handle sensitive data, such as user information or financial transactions. Without proper authentication, anyone could access your services, potentially leading to data breaches and unauthorized access. This is where JWT (JSON Web Token) authentication comes in. JWTs provide a compact and secure way to transmit information between parties as a JSON object. By adding JWT authentication to our API gateway, we can ensure that only authenticated and authorized users can access our microservices.

In this section, I'll walk you through the step-by-step process of implementing JWT authentication in our Ocelot API gateway, ensuring that each microservice request is authenticated. Let's get started and secure our microservices like a pro!

# Install JWT Package
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="x.x.x" />
 # Create Authentication Key Section 
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Authentication": {
    "Key": "qa12ed34TgH67JkiYK89LOF4Gf6Hy98Gtyuj7Ki34F",
    "Issuer": "http://localhost:7003",
    "Audience": "http://localhost:7003"
  }
}

 # Register Bearer Token Service 
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        var key = Encoding.UTF8.GetBytes(builder.Configuration.GetSection("Authentication:Key").Value!);
        string issuer = builder.Configuration["Authentication:Issuer"]!;
        string audience = builder.Configuration["Authentication:Audience"]!;

        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = false,
            ValidateIssuerSigningKey = true,
            ValidIssuer = issuer,
            ValidAudience = audience,
            IssuerSigningKey = new SymmetricSecurityKey(key)
        };
    });

 # Add CORS 
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
    });
});
 

# Create Login and Register Endpoints in Controller

  using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.Collections.Concurrent;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace AuthenticationApi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class AccountController(IConfiguration config) : ControllerBase
    {
       
        private static ConcurrentDictionary<string,string> UserData { get; set; } = 
            new ConcurrentDictionary<string,string>();

        //api/account/login/{email}/{password}
        [HttpPost("login/{email}/{password}")]
        public async Task<IActionResult> Login(string email, string password)
        {
            await Task.Delay(500);
            var getEmail = UserData!.Keys.Where(e => e.Equals(email)).FirstOrDefault();
            if(!string.IsNullOrEmpty(getEmail))
            {
                UserData.TryGetValue(getEmail, out string? dbPassword);
                if (!Equals(dbPassword, password))
                    return BadRequest("Invalid credentials");

                string jwtToken = GenerateToken(getEmail);
                return Ok(jwtToken);
            }
            return NotFound("Email not found");

        }

        private string GenerateToken(string getEmail)
        {
            var key = Encoding.UTF8.GetBytes(config["Authentication:Key"]!);
            var securityKey = new SymmetricSecurityKey(key);
            var credential = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
            var claims = new[] { new Claim(ClaimTypes.Email, getEmail!) };
            var token = new JwtSecurityToken(
                issuer: config["Authentication:Issuer"],
                audience: config["authentication:Audience"],
                claims:claims,
                expires: null,
                signingCredentials: credential);

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


        [HttpPost("register/{email}/{password}")]
        public async Task<IActionResult> Register(string email, string password)
        {
            await Task.Delay(500);
            var getEmail = UserData!.Keys.Where(e => e.Equals(email)).FirstOrDefault();
            if (!string.IsNullOrEmpty(getEmail))
                return BadRequest("User already exist");

            UserData[email] = password;
            return Ok("User create successfully");
        }
    }
}

# Create Middleware To Check for Authentication Token In the Gateway
namespace Gateway.Middlewares
{
    public class TokenCheckerMiddleware(RequestDelegate next)
    {
        public async Task InvokeAsync(HttpContext context)
        {
            string requestPath = context.Request.Path.Value!;
            if (requestPath.Contains("account/login", StringComparison.InvariantCultureIgnoreCase)
                || requestPath.Contains("account/register", StringComparison.InvariantCultureIgnoreCase)
                || requestPath.Equals("/"))
            {
                await next(context);
            }
            else
            {
                var authHeader = context.Request.Headers.Authorization;
                if (authHeader.FirstOrDefault() == null)
                {
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    await context.Response.WriteAsync("Sorry, Access denied");
                }
                else
                {
                    await next(context);
                }
            }

        }
    }
}



 # Add Authentication Routes to the Ocelot 
{
"Routes": [
  {
    "DownstreamPathTemplate": "/api/user",
    "DownstreamScheme": "http",
    "DownstreamHostAndPorts": [
      {
        "Host": "localhost",
        "Port": 7001
      }
    ],
    "UpstreamPathTemplate": "/api/user",
    "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ]
  },
  {
    "DownstreamPathTemplate": "/api/weatherforecast",
    "DownstreamScheme": "http",
    "DownstreamHostAndPorts": [
      {
        "Host": "localhost",
        "Port": 7002
      }
    ],
    "UpstreamPathTemplate": "/api/weatherforecast",
    "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ]
  },
  {
    "DownstreamPathTemplate": "/api/account/{email}/{password}",
    "DownstreamScheme": "http",
    "DownstreamHostAndPorts": [
      {
        "Host": "localhost",
        "Port": 7003
      }
    ],
    "UpstreamPathTemplate": "/api/account/{email}/{password}",
    "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ]
  }
],
"GlobalConfiguration": {
  "BaseUrl": "https://localhost:7000"
}

Conclusion

Great job! We've successfully added JWT authentication to our microservices architecture using Ocelot as our API gateway. By doing this, we've significantly enhanced the security of our application, ensuring that only authenticated users can access our services.

In our scenario, where sensitive data and transactions are involved, JWT authentication is crucial. It helps us prevent unauthorized access, protects our data, and ensures a secure communication channel between clients and services. This extra layer of security is not just a best practice but a necessity in today's world of microservices and distributed systems.

Remember, implementing security measures like JWT authentication is an ongoing process. Stay tuned for more videos where we'll dive even deeper into securing our applications and exploring advanced techniques.



Comments

Popular Posts

Complete Employee Management System | .NET 8 Blazor Wasm & Web API - Perform CRUD, Print, PDF etc..

.NET 8 Clean Architecture with Blazor CRUD, JWT & Role Authorization using Identity & Refresh Token๐Ÿ”ฅ

Employee Management System | .NET 8 Blazor Wasm- Profile & real-time data retrieval. Update 1