.NET - Gerenciar tokens JWT com dotnet user-jwts


 Hoje veremos como criar e gerenciar tokens JWT no ambiente de desenvolvimento usando a ferramenta de linha de comando dotnet user-jwts.

No .NET 7.0 a implementação e configuração dos tokens JWT foi simplificada e agora ficou bem mais simples.

As opções de autenticação agora podem ser configuradas automaticamente diretamente do sistema de configuração, devido à adição de uma seção de configuração padrão ao configurar por meio da nova propriedade AddAuthentication na classe Program em WebApplicationBuilder :

var builder = WebApplication.CreateBuilder(args);

...

builder.Services.AddAuthentication().AddJwtBearer();

var app = builder.Build();

...

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Assim agora basta usar a instrução app.UseAuthorization para adicionar o middleware de autorização ao pipeline da aplicação. Esse middleware é responsável por habilitar a funcionalidade de autorização e controle de acesso na aplicação.

Assim não precisamos usar mais a instrução app.UseAuthentication em conjunto com UseAuthorization.

Para mostrar o recurso funcionando na prática vamos criar um projeto Web API chamado ApiJwtNet7 no VS 2022 usando o template padrão.

Teremos a solução contendo o controlador WeatherForecast que vai exibir aleatóriamente a previsão do tempo.

A seguir vamos incluir no projeto o pacote : Microsoft.AspNetCore.Authentication.JwtBearer

E definir na classe Program a inclusão do  o serviço de autenticação ao contêiner de injeção de dependência no contexto de configuração e usar o novo método AddJwtBearer():

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddAuthentication().AddJwtBearer();

var app = builder.Build();
// Configure the HTTP request pipeline.

if
(app.Environment.IsDevelopment())
{
  app.UseSwagger();
  app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
 

Vamos incluir no controlador o atributo [Authorize] para restringir o acesso ao endpoint da API.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ApiJwtNet7.Controllers;
[ApiController]
[Authorize]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries = new[]
    {
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
}
...

A seguir vamos executar o projeto e tentar acessar o endpoint GET /WeatherForecast

Como era esperado obtemos um 401 - Unauthorized:

Pois precisamos fornecer um token Jwt para poder acessar o endpoint da API.

E como podemos obter este token ?

No .NET 7.0, no ambiente de desenvolvimento podemos obter o token usando a ferramenta de linha de comando dotnet user-jwts create

A ferramenta user-jwts é semelhante em conceito à ferramenta user-secrets, e pode ser usada para gerenciar valores para o aplicativo que são válidos apenas para o desenvolvedor na máquina local. Na verdade, esta ferramenta  utiliza a infraestrutura de user-secrets para gerenciar a chave com a qual os JWTs são assinados, garantindo que ela seja armazenada com segurança no perfil do usuário.

A ferramenta user-jwts oculta detalhes de implementação, como onde e como os valores são armazenados e pode ser usada sem conhecer esses detalhes. Os valores são armazenados em um arquivo JSON na pasta de perfil do usuário da máquina local:

%APPDATA%\Microsoft\UserSecrets\<secrets_GUID>\user-jwts.json  (No WIndows)

Para isso basta abrir uma janela no Package Manager Console e se posicionar na pasta do projeto e emitir o comando : dotnet user-jwts create

Vemos a acima o token gerado e podemos usá-lo para acessar o endpoint.

Para fazer isso no ambiente do Swagger vamos alterar a configuração na classe Program incluindo o código a seguir que altera a configuração para AddSwaggerGen:

using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "weatherforecast", Version = "v1" });

    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
    {
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Description = @"JWT Authorization header using the Bearer scheme.
                       \r\n\r\n Enter 'Bearer'[space].
                    \r\n\r\nExample: \'Bearer 12345abcdef\'",
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                          new OpenApiSecurityScheme
                          {
                              Reference = new OpenApiReference
                              {
                                  Type = ReferenceType.SecurityScheme,
                                  Id = "Bearer"
                              }
                          },
                         new string[] {}
                    }
                });
});

builder.Services.AddAuthentication().AddJwtBearer();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

Agora podemos executar o projeto novamente e veremos agora o botão com ícone de um cadeado :

Onde podemos clicar para poder inserir o token gerado copiando e colando o seu valor após a palavra Bearer:

Agora basta clicar no botão Authorize e acessar o endpoint com sucesso.

Podemos obter o mesmo resultado abrindo um prompt de comandos e estando na pasta do projeto emitir o comando:

curl -i -H "Authorization: Bearer {token}" https://localhost:{port}/WeatherForecast

Onde {token} é o token gerado:

Podemos usar o comando:  dotnet user-jwts print {ID} --show-all para exibir informações sobre o JWT.  Aqui ID é o ID do token gerado após usar o comando user-jwts.

Se espiarmos o conteúdo do arquivo appsettings.Development.json veremos a adição de uma seção Authentication que foi gerada quando usamos a ferramenta dotnet user-jwts :

{
"Logging": {
   "LogLevel": {
    
"Default": "Information",
     "Microsoft.AspNetCore"
: "Warning"
   }
  },
 
"Authentication": {
    
"Schemes": {
       
"Bearer": {
          
"ValidAudiences": [
              
"http://localhost:61224",
              
"https://localhost:44311",
              
"http://localhost:5240",
              
"https://localhost:7013"
              ],
             
"ValidIssuer": "dotnet-user-jwts"
          }
       }
    }
 }
 

Como isso está no arquivo appsettings.development.js, os tokens criados nunca funcionarão na produção como um portão de segurança adicionado. De fato, se decodificarmos nosso JWT que ele gerou, podemos ver que ele não tem nada de especial, exceto o nome de usuário da máquina que ele usa como id exclusivo.

Quando a aplicação é executada em um ambiente de desenvolvimento, a ASP.NET Core lê tanto o arquivo appsettings.json quanto o appsettings.Development.json. As configurações definidas no appsettings.Development.json vão complementar ou substituir as configurações do appsettings.json específicas desse ambiente.

Se houver configurações com o mesmo nome nos dois arquivos, as configurações do appsettings.Development.json terão prioridade e substituirão as configurações correspondentes do appsettings.json. Isso permite que você tenha configurações específicas para o ambiente de desenvolvimento sem afetar os outros ambientes.

Com isso temos uma forma bem simples de gerar tokens jwt no ambiente de desenvolvimento para realizar testes.

Pegue o projeto aqui: ApiJwtNet7.zip

"Porque Deus nos escolheu nele antes da criação do mundo, para sermos santos e irrepreensíveis em sua presença"
Efésios 1:4


E estamos conversados.

Referências:


José Carlos Macoratti