.NET 5 + ASP.NET Core + JWT + Refresh Tokens: exemplo de implementação
Já abordei a utilização de Refresh Tokens em APIs REST criadas com o ASP.NET Core em 2 artigos anteriores neste blog:
ASP.NET Core 3.1 + JWT + Refresh Tokens: exemplo de implementação
ASP.NET Core 2.0 + JWT: implementando Refresh Tokens
A seguir estão as motivações que explicitei no segundo link para uso de um Refresh Token:
E por que implementaríamos Refresh Tokens?
O fato de um token possuir um tempo de validade pré-determinado pode ser uma limitação em algumas situações. Expirado tal token um novo deverá ser gerado, a fim de permitir que se continue a interação com os recursos providos por uma API.
Soluções para contornar este comportamento existem e envolvem o uso de Refresh Tokens:
- Um valor adicional (um segundo token) será gerado, a fim de permitir a solicitação posterior de um novo token de acesso atualizado;
- Este procedimento dispensa a necessidade de se repetir todo o processo de autenticação que aconteceu durante a obtenção do token inicial, contribuindo assim para uma maior performance.
Neste novo post trago agora um exemplo que tomou como base os projetos indicados nos links anteriores, implementando uma API REST com .NET 5 + ASP.NET Core e mantendo o uso do Redis para controlar os tokens (com a expiração do Refresh Token estando condicionada à própria instância deste NoSQL). A aplicação em questão já foi disponibilizada no GitHub:
https://github.com/renatogroffe/ASPNETCore5_JWT-Identity-RefreshTokens
E como o assunto deste artigo é JWT, aproveito esse espaço para um convite…
No dia 26/05/2021 (quarta) às 21:00 — horário de Brasília — teremos mais um evento online e gratuito no Canal .NET.
Javascript Object Signing and Encryption (JOSE) é um framework. É nessa família que encontramos o JWT (JSON Web Token) e seus componentes.
Nesta live com o MVP Bruno Brito iremos abordar todos os componentes da família JOSE: JWA, JWK, JWE, JWKS. E como esse framework é o coração da segurança do OAuth2 & OpenId Connect. Além disso serão discutidos outros cenários de uso, como integrações de APIs!
Para participar faça sua inscrição no link a seguir, a transmissão acontecerá via YouTube:
No arquivo appsettings.json estão:
- A string de conexão para a instância do Redis (ConexaoRedis em ConnectionStrings);
- A seção TokenConfigurations com configurações empregadas na manipulação e geração dos tokens;
- No parâmetro AccessRole foi indicada a Role do ASP.NET Core Identity que determina o direito de acesso ou não à API REST;
- Em SecretJWTKey foi definida uma chave de criptografia para a geração do token;
- Em Seconds está o tempo em segundo de validade do token JWT, ao passo que em FinalExpiration temos o tempo em segundos de expiração do Refresh Token.
A configuração habilitando o uso de JWT acontecerá por meio de uma extensão definida na classe JwtSecurityExtension (método AddJwtSecurity). A pasta Security do projeto contém todos os recursos empregados nesta funcionalidade:
Na classe Startup foram efetuados os seguintes ajustes:
- Em ConfigureServices a chamada a AddDistributedRedisCache (linha 26) habilita o uso de cache com Redis, um pré-requisito para se trabalhar nesta implementação com Refresh Tokens;
- Ao invocar AddDbContext (linha 35) informando o tipo APISecurityDbContext estamos configurando o uso do ASP.NET Core Identity com Entity Framework Core InMemory (o SQL Server ou outro banco de dados suportado constituem outras possibilidades);
- Uma instância de TokenConfigurations foi gerada (a partir da linha 38) deserializando as configurações definidas em appsettings.json;
- E a chamada a AddJwtSecurity irá proceder com as configurações de objetos necessários à manipulação de tokens JWT e Refresh Tokens;
- Em Configure foi acionado o método Initialize do tipo IdentityInitializer (linha 73) com o objetivo de gerar uma credencial de acesso para testes;
- Por fim, ao invocar o método UseAuthorization estabelecemos que o acesso protegido a APIs estará habilitado.
O Controller (LoginController) correspondente à API de autenticação receberá as credenciais de através de uma chamada POST:
Já na API protegida (ContadorController) podemos notar a presença do atributo Authorize, com o valor Bearer indicando a necessidade de uso de tokens:
O fluxo de funcionamento desta implementação será mais bem compreendido com os testes demonstrados a seguir.
Inicialmente será enviada uma requisição à API responsável pela autenticação e fornecimento de um novo token. Utilizar para isto o seguinte conteúdo JSON no corpo da requisição:
Uma requisição HTTP POST com este conteúdo apresentará como resultado:
O contéudo de accessToken é justamente o token baseado em JWT utilizado no header de solicitações HTTP direcionadas à API de contagem de acessos. Sucessivas requisições enquanto este token for válido trarão resultados similares ao seguinte:
Passados pouco mais de 1 minuto (tempo que o token já estará expirado) uma tentativa de consulta ao contador de acessos resultará em um erro de código 401 (Unauthorized):
Podemos então nos valer do Refresh Token retornado no início dos testes, a fim de conseguir um novo token baseado em JWT e evitar a execução de todo o processo de autenticação do zero:
Como resultado de uma solicitação com este conteúdo JSON no corpo temos novos valores para accessToken e refreshToken:
Uma nova consulta ao contador de acessos com este segundo accessToken resultará em sucesso: