Antes de começarmos, se você não acompanhou a Parte 1 e a Parte 2, sugiro que você revise os entendimentos para seguir na configuração da API de Pagamento, que será o foco principal desta Parte 3.
Na Parte 1 eu falo sobre ApiResources e Clients, a configuração feita lá é de um Client que é acessado através de um Id e uma senha (Secret) que possui um escopo para a ApiResource chamada Payment. Traduzindo isso significa que:
Uma aplicação cliente pode requisitar um token ao IdentityServer com um usuário e senha, indicando que ele quer o token que permite o acesso a uma API que possui o escopo de pagamento, somente esse token gerado com esse Client permite o acesso à API de pagamento.
Criação do projeto da API
Para criar o projeto, siga as imagens a seguir:
Dependendo de quando você instalou o SDK do .NET Core 3.1 o template pode vir com classes e controllers que são desnecessárias, então basta apagá-las. Eu dei o nome de Payment.Api ao projeto e o coloquei dentro da pasta src > Web.
NuGet Package
Através do NuGet, adicione o pacote
IdentityServer4.AccessTokenValidation
Statup.cs
Configure o arquivo Startup.cs conforme abaixo.
using IdentityServer4.AccessTokenValidation;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Payment.Api
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization();
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetSection("IdentityServerAuthentication:Authority").Value;
options.RequireHttpsMetadata = false;
options.ApiName = Configuration.GetSection("IdentityServerAuthentication:ApiName").Value;
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Observação: Tome cuidado com a ordem das linhas, isso faz diferença e pode ser o diferencial no funcionamento.
OpenID Connect
O método “AddIdentityServerAuthentication” usa por baixo dos panos uma camada de identificação simples que é uma abstração para a implementação do protocolo OAuth 2.0. É esta camada que permite que uma apliação cliente possa solicitar autorização a um serviço provedor de autenticação também conhecido como service provider.
Quando o token é passado na chamada da API, através do OpenID Connect é realizada uma requisição ao IdentityServer (service provider) que vai autenticar a chamada e permitir a autorização de acesso à controller.
Para o OpenID Connect, você vai ver nomenclaturas como oidc que significa a mesma coisa, bem como para o IdentityServer, você vai encontrar nomenclaturas como idsrv.
Github
Caso tenha dúvidas, baixe o código no Github. Eu estou separando cada uma das etapas destes posts em branchs distintos, este projeto da API usa a solution FullProjectOne-Web.sln e está no branch payment-api.
appsettings.json
Neste arquivo de configuração, já estão pré-definidas a configuração do Authority que é o serviço ao qual o token será verificado se é confiável ou não, no caso esta atribuição é do IdentityServer, por isso aqui vai a URL dele e está a configuração do nome do ApiResource que essa API permite a conexão.
Quando uma aplicação cliente requisita um token ao IdentityServer com um usuário e senha e especificando o escopo de qual API será acessada com esse token é este escopo do ApiResource que será validado. Você pode achar estranho que a configuração ApiName só permite um nome (uma string), não vai permitir que você faça com que uma API atenda a duas ou mais funções diferentes, pois acaba ferindo um dos princípios dos microsserviços, o da responsabilidade única, uma API possui um único objetivo e uma única responsabilidade.
Configure o arquivo appsettings.json conforme a seguir.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"IdentityServerAuthentication": {
"Authority": "http://localhost:5005",
"ApiName": "payment"
}
}
Executando o IdentityServer
Antes de testarmos a API de Pagamento, precisamos que o IdentityServer esteja em execução, para isso, você pode executá-lo usando os comandos do Powershell conforme imagem a seguir, lembrando que o branch (identityserverinmemory) do IdentityServer é diferente da API, você pode conferir no Github.
Token para o escopo da API de Pagamento
A imagem a seguir mostra como buscar o token que permite acessar a API payment que configuramos.
O token é o conjunto de caracteres do json da imagem, começa com “eyJhb….”.
Este token é do tipo Bearer e permite acesso à API que pertence ao escopo payment que falamos nos tópicos anteriores deste post. O tipo Bearer também pode ser chamado de token de autenticação, é um schema de autenticação utilizando protocolo HTTP e considerado seguro.
O token é válido por 3600 segundos (1 hora), depois desse período ele passa a ser inválido e você precisa solicitar um novo token.
Particularidades sobre o token
- Se você alterar o certificado digital, o token deixará de ser válido, seja o arquivo *.rsa ou o *.pfx, qualquer mudança vai invalidar o token gerado antes da alteração.
- Você pode se perguntar também sobre o refresh token, para o grant_type client_credentials o refresh não é permitido, papo para outra hora, quando nos aprofundaremos nessas configurações e quais as opções permitidas, bem como aumentar o tempo que o token é válido.
Curiosidade sobre o token
Pegue token e entre no site da JWT, coloque o token no campo Encoded e ao lado direito você vai poder ver algumas informações que estão contidas no token e que são públicas.
Minha sugestão é que você pesquise sobre o assunto, você vai descobrir coisas incríveis.
Controller da API de Pagamento
Vamos configurar a controller conforme o código a seguir.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace Payment.Api.Controllers
{
[Authorize]
[Route("api/[controller]")]
public class PaymentController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
}
Veja que no código estou forçando a obrigatoriedade de estar autorizado ([Authorized] como atributo da classe), também especifiquei uma rota.
Chamando a API de Pagamento
Execute o projeto, por linha de comando, F5, da forma que você quiser, eu configurei a API para a porta 5002.
Usei novamente o Postman para fazer o GET conforme imagem a seguir, porém nossa chamada precisa estar autorizada e para isso vamos configurar o Postman para autenticar com o tipo Bearer (falei mais acima sobre ele) e utilizando o token solicitado ao IdentityServer.
Na aba Authorization, selecione o tipo Bearer Token e coloque o token no textbox em branco, depois disso clique em “Send”. Deve retornar um json com os valores “value1” e “value2”.
Você pode testar com o token gerado depois de 1 hora, sem token e outras formas criativas, o resultado deve ser o erro 401 (não autorizado).
PS: Este projeto está em constante evolução, então pode haver diferenças entre o código do gist e o código real. Além disso, estou codificando esta API para que ela possua a implementação do pattern facade para múltiplas formas de pagamento, para que tenha a camada anti-corrupção para termos um exemplo mais próximo do mundo real.
Continua em ASP.NET Core 3.1 — IdentityServer4 — EF-support (Parte 4).