No desenvolvimento de APIs com ASP.NET Core, é comum encontrar dois desafios relacionados à autenticação JWT. O primeiro é a necessidade de digitar manualmente o prefixo "Bearer " ao inserir o token no Swagger UI, o que pode ser inconveniente. O segundo, mais crítico, ocorre quando um token expira: o front end frequentemente receba apenas uma exceção genérica, forçando o usuário a recarergar a página manualmente para efetuar um novo login, gerando uma experiência ruim.
A solução envolve customizar os eventos do handler de autenticação JWT no ASP.NET Core. O trecho a seguir demonstra a configuração principal, incluindo a definição de políticas de autorização, parâmetros de validação do token e a manipulação de eventos como OnAuthenticationFailed e OnMessageReceived.
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Text;
namespace ProjetoApi.Configuracao
{
public static class ConfiguracaoAutenticacao
{
public static IServiceCollection ConfigurarAutenticacaoJwt(this IServiceCollection servicos, IConfiguration configuracao)
{
servicos.AddAuthorization(opt =>
{
opt.AddPolicy("SomenteUsuarios", policy => policy.RequireRole("Usuario"));
opt.AddPolicy("AdminOuSistema", policy => policy.RequireRole("Administrador", "Sistema"));
});
var chaveBase64 = configuracao["Jwt:Chave"];
var chaveBytes = Encoding.ASCII.GetBytes(chaveBase64);
var chaveAssinatura = new SymmetricSecurityKey(chaveBytes);
var parametrosValidacaoToken = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = chaveAssinatura,
ValidateIssuer = true,
ValidIssuer = configuracao["Jwt:Emissor"],
ValidateAudience = true,
ValidAudience = configuracao["Jwt:Audiencia"],
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5),
RequireExpirationTime = true
};
servicos.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opcoes =>
{
opcoes.TokenValidationParameters = parametrosValidacaoToken;
opcoes.Events = new JwtBearerEvents
{
OnAuthenticationFailed = contexto =>
{
if (!contexto.Response.Headers.ContainsKey("Access-Control-Allow-Origin"))
{
contexto.Response.Headers.Add("Access-Control-Allow-Origin", "*");
}
contexto.Response.Headers.Add("X-Token-Expirado", "true");
contexto.Response.Headers.Add("X-Token-Valido", "false");
return Task.CompletedTask;
},
OnMessageReceived = contextoRequisicao =>
{
var cabecalhoAuth = contextoRequisicao.HttpContext.Request.Headers["Authorization"].ToString();
if (!string.IsNullOrEmpty(cabecalhoAuth) && !cabecalhoAuth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
var valorFormatado = $"Bearer {cabecalhoAuth}";
contextoRequisicao.HttpContext.Request.Headers["Authorization"] = valorFormatado;
}
return Task.CompletedTask;
}
};
});
return servicos;
}
}
}
Para que o frontend consiga ler os cabeçalhos de resposta personalizados (como X-Token-Expirado) em requisições de outro domínio, a política de CORS precisa expô-los explicitamente. A configuração abaixo permite qualquer origem e expõe os cabeçalhos relevantes.
using Microsoft.Extensions.DependencyInjection;
namespace ProjetoApi.Configuracao
{
public static class ConfiguracaoCors
{
public static IServiceCollection PermitirCorsPersonalizado(this IServiceCollection servicos)
{
servicos.AddCors(opcoes =>
{
opcoes.AddPolicy("PoliticaCorsPermissiva", construtor =>
construtor
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("X-Token-Expirado", "X-Token-Valido"));
});
return servicos;
}
}
}
Por fim, para aprimorar a experiência de desenvolvimento, configura-se o Swagger para gerar a documentação da API. A seguir, a ativação do middleware do Swagger UI no pipeline da aplicação.
app.UseSwagger();
app.UseSwaggerUI(configuracao =>
{
configuracao.SwaggerEndpoint("/swagger/v1/swagger.json", "API do Projeto - v1");
configuracao.RoutePrefix = string.Empty; // Acessa na raiz do site
});
A integração com a autenticação JWT no Swagger é feita na fase de serviços. O código a seguir configura o gerador de documentação e adiciona um botão para inserir o token no topo da interface do Swagger UI, permitindo que o valor seja enviado no cabeçalho Authorization das requisições de teste.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using System;
using System.IO;
using System.Reflection;
namespace ProjetoApi.Configuracao
{
public static class ConfiguracaoSwagger
{
public static IServiceCollection ConfigurarDocumentacaoSwagger(this IServiceCollection servicos)
{
servicos.AddSwaggerGen(configurador =>
{
configurador.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Documentação da API do Projeto",
Version = "1.0"
});
var nomeArquivoXml = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var caminhoArquivo = Path.Combine(AppContext.BaseDirectory, nomeArquivoXml);
configurador.IncludeXmlComments(caminhoArquivo);
configurador.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "Insira o token JWT com o prefixo 'Bearer '. Exemplo: 'Bearer eyJhb...'",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
configurador.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
return servicos;
}
}
}