.NET Core + JWT + Polly + Refit: consumindo APIs seguras com simplicidade e resiliência
Em um artigo anterior já abordei o uso da biblioteca Refit no consumo de APIs REST. O objetivo nesta ocasião foi demonstrar que a mesma constitui uma excelente alternativa ao uso da classe HttpClient (internamente o próprio Refit se vale de instâncias deste tipo), resultand em uma codificação muito mais simples e concisa:
ASP.NET Core + JWT + Refit: consumindo uma API protegida de forma descomplicada
O projeto Polly constitui outro exemplo de solução extremamente útil para Desenvolvedores .NET, simplificando em muito a implementação de mecanismos para tratamento de falhas e contribuindo para a construção de aplicações resilientes. Esta biblioteca também foi tema de um artigo que publiquei neste blog:
.NET Core + Polly + JWT: tratando de forma resiliente a expiração de tokens
Neste novo artigo demonstro como combinar o uso de Polly e Refit em uma aplicação .NET Core, possibilitando assim uma maior simplicidade e resiliência no consumo de uma API REST protegida por JWT e Refresh Tokens.
Implementando o consumo da API com Refit e Polly
A aplicação detalhada nesta seção fará uso da API disponibilizada no repositório a seguir:
Já comentei sobre este mesmo projeto e o fluxo de consumo de uma API REST protegida por JWT e Refresh Tokens no seguinte post:
ASP.NET Core 3.1 + JWT + Refresh Tokens: exemplo de implementação
Quanto ao uso de Refit, o projeto aqui apresentado é uma variação do exemplo abordado no artigo que já mencionei sobre esta biblioteca.
A interação com a API protegida acontecerá na classe APIProdutoClient:
A autenticação e mesmo a obtenção de um novo token de acesso (JWT) via Refresh Token acontecerá por meio da interface ILoginAPI:
Em APIProdutoClient um objeto será gerado com base em tal interface, empregando para isto a classe RestService disponibilizada pelo Refit (através de uma chamada ao método For):
No método AutenticarComSenha podemos observar a autenticação empregando usuário e senha, com uma codificação muito mais simples do que a baseada na classe HttpClient:
A operação AutenticarComRefreshToken conta com um comportamento similar, porém se vale do Refresh Token para a obtenção de um novo token de acesso (JWT):
Foi implementada ainda uma Policy baseada no pattern Retry para tratamento de falhas envolvendo o uso de JWT, empregando para isto a classe AsyncRetryPolicy da biblioteca Polly e considerando em HandleInner exceções baseadas na classe ApiException (tipo este disponibilizado pelo Refit):
- Conforme podemos observar no método CreateAccessTokenPolicy, uma resposta com status de Unauthorized (erro HTTP 401) fará com que se proceda com o envio do Refresh Token (via método AutenticarComRefreshToken);
- Caso se trate de um Refresh Token inválido (o mais provável é que tenha expirado), a autenticação convencional ocorrerá através do método AutenticarComSenha.
A interface IProdutosAPI será empregada na interação com a API de produtos. Todos os métodos definidos neste tipo recebem como parâmetro o token de acesso (JWT), o qual está representado pela referência token (esta última marcada com o atributo Header):
Um método de extensão chamado ExecuteWithRefreshTokenAsync também foi criado (estendendo a classe AsyncRetryPolicy), tendo por objetivo simplificar o tratamento de falhas ao executar códigos sujeitos à expiração de tokens. O dicionário manipulado aqui evitará instruções duplicadas armazenando o token de acesso (JWT) e o Refresh Token no objeto de contexto utilizado por AsyncRetryPolicy:
Quanto ao consumo da API de produtos e o tratamento de falhas é necessário destacar:
- No construtor de APIProdutosClient será gerada uma instância baseada em AsyncRetryPolicy (_jwtPolicy) para a execução de chamadas a esta API e já considerando todo o fluxo de tratamento de erros no caso de expiração dos tokens;
- Um objeto baseado na interface IProdutosAPI será gerado por meio de uma chamada ao método For da classe RestService;
- As ações dos métodos IncluirProduto e ListarProdutos foram bastante simplificadas tanto pelo uso de Refit (com um código mais enxuto se comparado à utilização de HttpClient), quanto por empregar a biblioteca Polly (dispensando blocos try-catch e loops para tentativas adicionais).
Na imagem a seguir há um exemplo de teste desta aplicação em que é possível observar:
- Durante a segunda ação que envolve o cadastramento de um produto o token de acesso (JWT) e o Refresh Token expiraram, com isso resultando em uma nova autenticação;
- Já na terceira ação foi esperada a expiração do token de acesso (JWT). Desta vez houve sucesso no uso do Refresh Token, com a exibição na sequência do resultado da consulta aos produtos cadastrados.
O código-fonte desta aplicação também foi disponibilizado no GitHub:
.NET Core 3.1 + Refit + Consumo de API REST + JWT + Refresh Tokens + Código Resiliente com Polly