Nesses últimos dias, me deparei com um desafio e por isso resolvi escrever esse post. O desafio foi: como podemos utilizar o Azure AD para proteger um gateway feito com o Ocelot?

Antes de começarmos, sugiro um pequeno estudo sobre o que são gateways e a lib Ocelot para .NET!

Vamos inicialmente criar um web api para servir como fonte de dados. Em uma aplicação de produção, essa aplicação não seria exposta na internet, e poderia ser consumida (ou não, dependendo do caso) via gateway.

dotnet new webapi -n App.Api

Nesse projeto, essa web api será protegida somente pelo gateway, não precisando validar access tokens para cada requisição.

Para o gateway, vamos criar um projeto com o template web do .NET:

dotnet new web -n App.Gateway

E adicionaremos os seguintes pacotes NuGet:

dotnet add package Microsoft.Identity.Web
dotnet add package Ocelot

Eu já escrevi uma série de posts sobre como podemos proteger nossas aplicações com o Microsoft identity platform. Vamos usá-la como base para essa solução!

Como mostrado na série de posts de referência, vamos criar dois registros de aplicativos no Azure AD:

  • App.Gateway;
  • App.Client;

O App.Gateway será o aplicativo web protegido, e o App.Client será o aplicativo que irá fazer requisições para o gateway. Na nossa solução, a web api não será exposta na internet, por tanto não iremos protege-lá com o Azure AD.

Para o App.Gateway, vamos criar um App Role chamado Admin:

Vamos permitir que essa role seja atribuída a grupos ou usuários. A ideia é atribuirmos essa role ao nosso usuário para podermos ser autorizados a chamar os endpoints do gateway.

Para o client, vamos seguir como no post de referência: criar um app registration para ser usado pelo postman.

O código da nossa web api vai ser mantido como está, já para a aplicação do gateway, modificar o Program.cs:

using App.Gateway;
using Microsoft.Identity.Web;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);

builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);

builder.Services.AddOcelot(builder.Configuration);

builder.Services.DecorateClaimAuthoriser();

var app = builder.Build();

app.UseAuthentication();

app.UseOcelot().Wait();

app.Run();
  • Adicionamos o arquivo ocelot.json ao provedor de configurações;
  • Protegemos a aplicação com o Microsoft identity platform;
  • Configuramos o Ocelot;

Além disso, observe que adicionamos o builder.Services.DecorateClaimAuthoriser(). Esse método de extensão fará com que o arquivo ocelot.json seja lido de forma correta para as chaves do tipo "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" (mais detalhes aqui!).

using Ocelot.Authorization;

namespace App.Gateway;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection DecorateClaimAuthoriser(this IServiceCollection services)
    {
        var serviceDescriptor = services.First(x => x.ServiceType == typeof(IClaimsAuthorizer));
        services.Remove(serviceDescriptor);

        var newServiceDescriptor = new ServiceDescriptor(serviceDescriptor.ImplementationType, serviceDescriptor.ImplementationType, serviceDescriptor.Lifetime);
        services.Add(newServiceDescriptor);

        services.AddTransient<IClaimsAuthorizer, ClaimAuthorizerDecorator>();

        return services;
    }
}
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using System.Security.Claims;
using Ocelot.Authorization;
using Ocelot.Responses;

namespace App.Gateway;

public class ClaimAuthorizerDecorator : IClaimsAuthorizer
{
    private readonly ClaimsAuthorizer _authoriser;

    public ClaimAuthorizerDecorator(ClaimsAuthorizer authoriser)
    {
        _authoriser = authoriser;
    }

    public Response<bool> Authorize(ClaimsPrincipal claimsPrincipal,
                                    Dictionary<string, string> routeClaimsRequirement,
                                    List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
    {
        var newRouteClaimsRequirement = new Dictionary<string, string>();
        foreach (var kvp in routeClaimsRequirement)
        {
            if (kvp.Key.StartsWith("http///"))
            {
                var key = kvp.Key.Replace("http///", "http://");
                newRouteClaimsRequirement.Add(key, kvp.Value);
            }
            else
            {
                newRouteClaimsRequirement.Add(kvp.Key, kvp.Value);
            }
        }

        return _authoriser.Authorize(claimsPrincipal, newRouteClaimsRequirement, urlPathPlaceholderNameAndValues);
    }
}

Finalmente temos o arquivo ocelot.json:

{
    "Routes": [
      {
        "DownstreamPathTemplate": "/WeatherForecast",
        "DownstreamScheme": "http",
        "DownstreamHostAndPorts": [
          {
            "Host": "localhost",
            "Port": 5014
          }
        ],
        "UpstreamPathTemplate": "/api/weather-forecast",
        "UpstreamHttpMethod": [ "Get" ],
        "AuthenticationOptions": {
          "AuthenticationProviderKey": "Bearer"
        },
        "RouteClaimsRequirement": {
          "http///schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin"
        }
      }      
    ],
    "GlobalConfiguration": {
      "BaseUrl": "https://localhost:7096"
    }
  }
  • Ajuste o valor da chave Port com a porta correta que você vai rodar a web api;
  • O gateway retornará 403 se a requisição para o caminho /api/weather-forecast não possuir a role Admin;

Para finalizar, precisamos adicionar ao appsettings.json os dados do registro de aplicativo App.Gateway:

{ 
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<your-domain>",
    "ClientId": "<your-client-id>",
    "TenantId": "<your-tenant-id>"
},

Dito isso, vamos executar o gateway e a web api:

dotnet run

Como mostrado no post de referência, vamos adquirir um novo access token via postman e fazer a requisição ao gateway.

Observe que estamos autenticados, porém, não possuímos a role Admin no access token. Vamos ao portal do Azure, na aba "Enterprise Application", e adicionar a app role Admin do App.Gateway ao usuário que estamos usando.

De volta ao postman, podemos fazer uma aquisição de um novo access token. Utilizando o site jwt.io, podemos ver que o access token recebido possui a role Admin.

Vamos refazer a requisição ao gateway utilizando esse novo access token:

Tudo ocorreu bem! Dessa forma podemos acessar web apis através de um gateway feito em Ocelot e protegido com o Azure AD.

Você já pode baixar o projeto por esse link, e não esquece de me seguir no LinkedIn!

Até a próxima, abraços!

💡
Podemos te ajudar com uma revisão 100% gratuita do seu ambiente cloud.
Share this post