ASP .NET Core -  Usando o IdentityServer4 - III


Neste artigo veremos como usar o IdentityServer4 com a ASP .NET Core.

Continuando o artigo anterior vamos realizar a implementação da segurança com o IdentitySever4.

Implementando a segurança com o IdentityServer4

Abrindo o projeto Web API em nossa solução vamos instalar o pacote IdentityServer4.AccessTokenValidation neste projeto.

Nota:  Essa biblioteca atualmente esta desatualizada e em produção considere substituir essa implementação conforme orienta o artigo :  https://leastprivilege.com/2020/07/06/flexible-access-token-validation-in-asp-net-core/

Para isso abra a janela do Package Manager Console e digite :  Install-Package IdentityServer4.AccessTokenValidation

A seguir vamos incluir o Middleware Authentication no projeto WebAPI definindo no método ConfigureServices do arquivo Startup o código a seguir :

services.AddAuthentication("Bearer")
   .AddIdentityServerAuthentication("Bearer", options =>
    {
       options.ApiName = "myApi";
       options.Authority = "https://localhost:44306";
   });

Neste código determinamos o nome do recurso - myAPI - que definimos na configuração do projeto IdentityServer, e,  informamos a URL na qual o IdentityServer vai ser executado e vai estar atendendo.

Agora inclua no método Configure do arquivo Startup do projeto WebAPI a ativação da autenticação e autorização :

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
     if (env.IsDevelopment())
    {
       app.UseDeveloperExceptionPage();
    }

     app.UseHttpsRedirection();
     app.UseRouting();
 
     app.UseAuthorization();
     app.UseAuthorization();


     app.UseEndpoints(endpoints =>
    {
       endpoints.MapControllers();
     });
}

Agora podemos incluir no controlador WeatherController o atributo Authorize de forma a permitir o acesso a este controlador somente aos usuários autenticados.

Estamos assim aplicando a segurança ao endpoint da nossa Web API.

Executando novamente o nosso projeto e fazendo um request GET no POSTMAN para a url localhost:4435/weatherForecast iremos obter o erro 401 Unauthorized:

Assim não temos acesso a API pois agora ela esta protegida.

Vamos então usar ao token de acesso que obtemos no POSTMAN para tentar acessar o endpoint : localhost:44335/weatherforecast

Para isso vamos usar o Header Authorization e escolher o tipo Bearer Token e a seguir copiar e colar o token de acesso  e a seguir clicar no botão Send.

Teremos o resultado abaixo onde vemos o status code 200 OK e os dados da API sendo exibidos:

Assim constatamos que nossa API esta segura usando o IdentityServer4 e que usando o token de acesso podemos acessar a API.

A seguir vamos incluir um novo projeto na solução que vai ser o Cliente que vai tentar acessar nossa Web API WeatherForecast.

Criando uma aplicação Web Cliente para acessar a API

Vamos então incluir na solução um no projeto  via menu File-> New Project;

Escolhendo o template ASP .NET Core Web Application  e informando o nome WebClient;

E usando o template ASP.NET Core Web App (Model-View-Controller) no ambiente do .NET 5.0 :

Ao final teremos dois projetos na solução :

  1. O projeto do IdentityServer chamado IdentityServer
  2. O projeto Web API chamado WebApi
  3. O projeto MVC chamado WebClient

Neste projeto vamos instalar o pacote :   Install-Package IdentityModel



O
IdentityModel é uma família de bibliotecas FOSS para construir clientes OAuth 2.0 e OpenID Connect.

Em seguida, precisamos de um serviço que possa se comunicar com o IdentityServer4 e solicitar um token de acesso com o qual o Projeto MVC possa acessar os dados da API.

No projeto WebClient vamos criar uma pasta chamada Services e nesta pasta vamos incluir a interface TokenService com o código abaixo:

using IdentityModel.Client;
using System.Threading.Tasks;

public interface ITokenService

{
   Task<TokenResponse> GetToken(string scope);
}

Note que TokenResponse é um tipo definido no IdentityModel.

A seguir vamos criar a classe concreta TokenService nesta pasta que implementa esta interface:

using IdentityModel.Client;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace WebClient.Services
{
    public class TokenService : ITokenService
    {
        private DiscoveryDocumentResponse _discDocument { get; set; }
        public TokenService()
        {
            using (var client = new HttpClient())
            {
                _discDocument = client.GetDiscoveryDocumentAsync("https://localhost:44306/.well-known/openid-configuration").Result;
            }
        }
        public async Task<TokenResponse> GetToken(string scope)
        {
            using (var client = new HttpClient())
            {
                var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
                {
                    Address = _discDocument.TokenEndpoint,
                    ClientId = "cwm.client",
                    Scope = scope,
                    ClientSecret = "secret"

                });
                if (tokenResponse.IsError)
                {
                    throw new Exception("Token Error");
                }
                return tokenResponse;
            }
        }
    }
}

Neste código a classe DIscoveryDocumentReponse vem com o pacote que instalamos anteriormente.

No construtor usamos uma instância de HttpClient para para obter os dados do documento do endpoint de configuração do OpenID IdentityServer.

Note que estamos definindo as URLs aqui, no entanto o ideal e fazer a definição no arquivo appsettings.json e usar o padrão IOptions para recuperá-los em tempo de execução.

Neste código também estamos usando os dados do cliente que foi definido na configuração do IdentityServer :

                    Address = _discDocument.TokenEndpoint,
                    ClientId = "cwm.client",
                    Scope = scope,
                    ClientSecret = "secret"

Para obter os dados da previsão do tempo vamos criar um nova classe para acomodar os dados.

No Projeto WebClient adicione uma nova classe na pasta Models chamada  WeatherModel com o seguinte trecho de código:

using System;
namespace WebClient.Models
{
    public class WeatherModel
    {
        public DateTime Date { get; set; }
        public int TemperatureC { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
        public string Summary { get; set; }
    }
}

A seguir no controlador HomeController vamos injetar no construtor uma instância do serviço ITokenService:

 public class HomeController : Controller
{
      private readonly ILogger<HomeController> _logger;
      public ITokenService _tokenService { get; }


      public HomeController(ILogger<HomeController> logger, ITokenService tokenService)
      {
           _logger = logger;
          _tokenService = tokenService;
      }

...
}

A seguir inclua um novo método Action neste controlador chamado Weather que vai conversar com a API segura e obter os dados:

         public async Task<IActionResult> Weather()
        {
            var data = new List<WeatherModel>();

            var token = await _tokenService.GetToken("myApi.read");

            using (var client = new HttpClient())
            {
                client.SetBearerToken(token.AccessToken);
                var result = await client.GetAsync("https://localhost:44335/weatherforecast");
                if (result.IsSuccessStatusCode)
                {
                    var model = await result.Content.ReadAsStringAsync();
                    data = JsonConvert.DeserializeObject<List<WeatherModel>>(model);
                    return View(data);
                }
                else
                {
                    throw new Exception("Failed to get Data from API");
                }
            }
        }

Este código faz o seguinte:

- Usa o serviço de token e conversa com o IdentityServer4 e recupera um token de acesso válido;
- Define o token de acesso para o cabeçalho JWT do HttpClient;
- Usa o cliente Http e conversa com a API segura para obter os dados meteorológicos.

Como estamos adicionando o token JWT, não devemos ter nenhum problema em autenticar o WebClient para usar o WebAPI, certo?

Aqui temos que incluir o pacote Newtonsoft.Json no projeto WebClient via menu Tools-> ..-> Manage Nuget Packages for Solution :

Agora temos que criar a respectiva view Weather.cshtml na pasta /Views/Home para este método Action.

@model List<WeatherModel>

@{
    ViewData["Title"] = "Weather";
}

<h1>Weather</h1>
<table class="table table-striped">
    @foreach (var weather in Model)
    {
        <tr>
            <td>@weather.Date</td>
            <td>@weather.Summary</td>
            <td>@weather.TemperatureC</td>
            <td>@weather.TemperatureF</td>
        </tr>
    }
</table>

Com a view criada só falta registrar o serviço TokenService no método ConfigureServices da classe Startup:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddSingleton<ITokenService, TokenService>();
        }

Ufa, enfim terminamos a autorização do cliente.

 Agora, de um Build e execute os 3 projetos na seguinte ordem :

  1. IdentityServer
  2. WebAPI
  3. WebClient.

No navegador do projeto WebClient, navegue até ./home/weather, e,  você deverá visualizar dos dados obtidos a partir da Web API protegida pelo IdentityServer4:

Desta forma estamos acessando a partir do projeto WebClient os dados do projeto WebAPI.

Pegue o projeto aqui:  AspnIdentityServer4_Client.zip

"Porque a lei foi dada por Moisés; a graça e a verdade vieram por Jesus Cristo."
João 1:17

Referências:


José Carlos Macoratti