top of page
Writer's pictureThe Tech Platform

.Net 5 Web API with Ocelot - Multiple Auth Schemes JWT

Ocelot its a great package which provide resources to build Api gateways. If you aren’t familiarized with this concept, check this link out, but in a nutshell, api gateway its a way to centralize Apis requests in a single Api, which one is responsible to forwarding the requests to others APIs.



Our goal in this tutorial is setup a Api gateway with isolateds securities to its microservices, in order to achieve that, in summary We gonna: 1 — Create and configure our APIs ProductsApi, which will listen port 5002; and CategoriesAPI, which will listen port 5001, they are going to be our microservices. 2- Create and configure our Api Gateway with Ocelot and JWT provider, it will listen port 5000.

As result, We going to have two microservices that will be requesting by a APIGateway, and this API will centralize the requests for them with segregated security.

All the projects needed to our solution are gonna based on .NET 5.

1 — Getting ready the microservices

The following image shows the solution structure: three ASP.NET Core Web Applications projects, using Web Api template. If you need some support for creating the projects, check this out.

You solution may look like that


1.1 — ProductsAPI

In ProductsAPI project, create a folder named Models, and this folder, create the following class, that will represent our Product model:


public class Product
{
   public int Id { get; set; } 
   public string Description { get; set; } 
   public int CategoryId { get; set; } 
}

To create some products and expose it, create ProductsController class in Controllers folder:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using ProductsAPI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ProductsAPI.Controllers
{    
    [ApiController]    
    [Route("[controller]")]
    public class ProductsController : ControllerBase    
    {        
        [HttpGet]
        public ActionResult<IEnumerable<Product>> GetProducts()        
        {
            int size = 10;
            var products = new List<Product>(size);
            
            for (int index = 1; index <= size; index ++)            
            {
                products.Add(new()                
                {
                    Id=index,
                    Description=$"Product {index}",
                    CategoryId=index                
                });            
           }
           
           return products;        
    }    
}


To finish our products microservice, change the file lauchSettings.json in Properties folder:


{
    "$schema": "http://json.schemastore.org/launchsettings.json",
    "iisSettings": 
    {
        "windowsAuthentication": false,
        "anonymousAuthentication": true,
        "iisExpress": 
        {
            "applicationUrl": "http://localhost:21666",
            "sslPort": 0    
        }  
    },
    "profiles": 
    {
        "ProductsAPI": 
        {
            "commandName": "Project",
            "dotnetRunMessages": "true",
            "launchBrowser": true,
            "launchUrl": "products",
            "applicationUrl": "http://localhost:5002",
            "environmentVariables": 
            {
                "ASPNETCORE_ENVIRONMENT": "Development"      
            }    
       }  
   }
}

1.2 — CategoriesAPI

In CategoriesAPI project, create Models folder too, then create class Category inside it:


public class Category
{
   public int Id { get; set; }
   public string Description { get; set; }
}

We need a controller as well, to return our categories. Create class CategoriesController in Controllers folder:

singCategoriesAPI.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace CategoriesAPI.Controllers
{    
    [ApiController]    
    [Route("[controller]")]
    public class CategoriesController : ControllerBase    
    {        
        [HttpGet]
        public ActionResult<IEnumerable<Category>> GetCategories()        
        {
            int size = 10;
            var categories = new List<Category>(size);
            
            for (int index = 1; index <= size; index ++)            
            {
                categories.Add(new()                
                {
                    Id = index,
                    Description=$"Category {index}"                
                });            
            }
            
            return categories;        
       }    
   }
}

Change the launchSettings.json in Properties folder:


{  
    "$schema": "http://json.schemastore.org/launchsettings.json",  
    "iisSettings": 
    {    
        "windowsAuthentication": false,    
        "anonymousAuthentication": true,    
        "iisExpress": 
        {      
            "applicationUrl": "http://localhost:65237",      "sslPort": 0    
        }  
    },  
    "profiles": 
    {    
        "CategoriesAPI": 
        {      
            "commandName": "Project",      
            "dotnetRunMessages": "true",      
            "launchBrowser": true,      
            "launchUrl": "categories",      
            "applicationUrl": "http://localhost:5001",      
            "environmentVariables": 
            {        
                "ASPNETCORE_ENVIRONMENT": "Development"      
            }   
       }  
   }
}

The purpose on change the lauchSettings files is for configure the port numbers and how the API is meant to be started, in this case, not with IIS Express, they will run in executable mode.

2 — API Gateway and its configuration

Now, in APIGateway project, let’s create a folder named Models, and the following classes within it: AuthToken, it represents the object which contains the token for the future requests to the APIGateway:


using System;
namespace APIGateway.Models
{
   public class AuthToken
   {
      public string Token { get; set; }
      public DateTime ExpirationDate { get; set; }
   }
}

And AuthUser, that will contain the properties to authenticate in APIGateway:


using System.ComponentModel.DataAnnotations;namespace APIGateway.Models
{
   public class AuthUser
   {
      [Required]
      public string Username { get; set; }
      [Required]
      public string Password { get; set; }
   }
}

We need install the packages for generate and control our tokens and to configure the API to be a gateway . Run the following commands in order to install them, or by Nuget Package Manager, as your prefer:


Install-Package Microsoft.AspNetCore.Authentication 
Install-Package Ocelot

Let’s create our services that will generate our tokens, create a folder named Services and then the following classes:

For generating tokens for requests to CategoriesAPI:

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

namespace APIGateway.Services
{    
    public class CategoriesApiTokenService    
    {        
        public AuthToken GenerateToken(AuthUser user)        
        {            
            var key = new SymmetricSecurityKey
             (Encoding.UTF8.GetBytes("my_categories_api_secret"));            
            var credentials = new SigningCredentials
                    (key, SecurityAlgorithms.HmacSha256Signature);            
            var expirationDate = DateTime.UtcNow.AddHours(2);            
            
            var claims = new[]            
            {                
                new Claim(ClaimTypes.Name, user.Username.
                                                    ToString()),                
                new Claim(JwtRegisteredClaimNames.Jti, Guid.
                                            NewGuid().ToString())            
            };            
            
            var token = new JwtSecurityToken(
                    audience: "categoriesAudience",                                              
                    issuer: "categoriesIssuer",                                              
                    claims: claims,                                              
                    expires: expirationDate,                                              
                    signingCredentials: credentials);            
            var authToken = new AuthToken();            
            authToken.Token = new JwtSecurityTokenHandler(). 
                                            WriteToken(token);            
            authToken.ExpirationDate = expirationDate;            
            
            return authToken;        
       }    
    }
 }

And for ProductsAPI:

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

namespace APIGateway.Services
{
    public class ProductsApiTokenService    
    {
        public AuthToken GenerateToken(AuthUser user)        
        {
            var key = new SymmetricSecurityKey
               (Encoding.UTF8.GetBytes("my_products_api_secret"));
            var credentials = new SigningCredentials
                (key, SecurityAlgorithms.HmacSha256Signature);
            var expirationDate = DateTime.UtcNow.AddHours(2);
        
            var claims = new []            
            {
            new Claim(ClaimTypes.Name, user.Username.ToString()),
            new Claim(JwtRegisteredClaimNames.Jti, 
            Guid.NewGuid().ToString())            
        };
        
        var token = new JwtSecurityToken(
                audience: "productsAudience",
                issuer: "productsIssuer",
                claims: claims,
                expires: expirationDate,
                signingCredentials: credentials);
       
        var authToken = new AuthToken();
        authToken.Token = new 
                      JwtSecurityTokenHandler().WriteToken(token);
        authToken.ExpirationDate=expirationDate;
        
        return authToken;        
    }    
 }
}

We need configure Ocelot and the authentication Schemes in Startup.cs:


using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using System;
using System.Text;

namespace APIGateway
{
    public class Startup    
    {
        public IConfiguration Configuration { get; }
        public IConfiguration OcelotConfiguration { get; }
        
        public Startup(IConfiguration configuration, 
        IHostEnvironment env)        
        {
            Configuration=configuration;        
            
            var builder = new ConfigurationBuilder();           
            builder.SetBasePath(env.ContentRootPath)                   
                .AddJsonFile("ocelot.json", optional: false, 
                                        reloadOnChange: true)                   
                .AddEnvironmentVariables();
            
            OcelotConfiguration=builder.Build();        
        }
        
        public void ConfigureServices(IServiceCollection services)        
        {
            services.AddAuthentication().AddJwtBearer
                            ("products_auth_scheme", options=>            
            {
            options.TokenValidationParameters = new 
                                    TokenValidationParameters()                
            {
                IssuerSigningKey = new SymmetricSecurityKey
               (Encoding.UTF8.GetBytes("my_products_api_secret")),
                ValidAudience="productsAudience",
                ValidIssuer="productsIssuer",
                ValidateIssuerSigningKey=true,
                ValidateLifetime=true,
                ClockSkew=TimeSpan.Zero                
            };            
        
        }).AddJwtBearer("categories_auth_scheme", options=>            
        {
            options.TokenValidationParameters = new 
                                    TokenValidationParameters()                
            {
                IssuerSigningKey = new SymmetricSecurityKey                                                            
             (Encoding.UTF8.GetBytes("my_categories_api_secret")),
                ValidAudience="categoriesAudience",
                ValidIssuer="categoriesIssuer",
                ValidateIssuerSigningKey=true,
                ValidateLifetime=true,
                ClockSkew=TimeSpan.Zero                
            };            
       });
       
       services.AddOcelot(OcelotConfiguration);
            
       services.AddControllers();        
   }
            
   // This method gets called by the runtime. Use this method to 
   configure the HTTP request pipeline.
   public void Configure(IApplicationBuilder app, 
   IWebHostEnvironment env)        
   {
       if (env.IsDevelopment())            
       {
           app.UseDeveloperExceptionPage();            
       }       
       app.UseRouting();
       
       app.UseAuthorization();
       app.UseAuthorization();
       
       app.UseEndpoints(endpoints=>            
       {
           endpoints.MapControllers();            
       });

Hold on, We are about to finish The last thing its tell to the Ocelot which APIs he gonna forwarding to and which authentication schemes them gonna obey. Create a json file named ocelot.json in Project root and change it:

{
    "Routes": [    
    {
        "DownstreamPathTemplate": "/categories",
        "DownstreamScheme": "http",
        "DownstreamHostAndPorts": [        
        {
            "Host": "localhost",
            "Port": 5001        
        }      
    ],
    "AuthenticationOptions": 
    {
        "AuthenticationProviderKey": "categories_auth_scheme",
        "AllowedScopes": []      
    },
    "UpstreamPathTemplate": "/apigateway/categories",
    "UpstreamHttpMethod": [ "Get" ]    
    },    
    {
        "DownstreamPathTemplate": "/products",
        "DownstreamScheme": "http",
        "DownstreamHostAndPorts": [        
        {
            "Host": "localhost",
            "Port": 5002        
        }      
    ],
    "AuthenticationOptions": 
    {
        "AuthenticationProviderKey": "products_auth_scheme",
        "AllowedScopes": []      
    },
    "UpstreamPathTemplate": "/apigateway/products",
    "UpstreamHttpMethod": [ "Get" ]    
    }  
    
    ],
    "GlobalConfiguration": 
    {
        "BaseUrl":  "http://localhost:5000"  
    }
}


Finally, the tests!!

After the whole code, We are able to test our application. Right click on Solution and choose Properties. Check Multiple startup projects and the launch order and Action for each project like image ilustrates below:

Use the arrows to change the launch order


I choose the Postman for requesting, but could be whatever another one, We need do Http POST and Http GET. Change the body with authentication properties, then authenticate for CategoriesAPI:

Since We have the token, We can consume categories endpoint. Create a request, change the url, authorization type to Bearer Token and paste the token previously recovered on Token field.

CategoriesAPI requested successfully!


Let’s authenticate to get token to access ProductsAPI:

Then, We are able to get the products:

ProductsAPI has returned the our products


A final test to check if the things are really working. Paste the token recovered in for the ProducstsAPI but in a CategoriesAPI request, the APIGateway need return Unauthorized:

Successfully failed!


The repository can be found in Github.



Source: Medium - Lucas Araujo


The Tech Platform

0 comments

Comments


bottom of page