.NET Core + Polly + JWT: tratando de forma resiliente a expiração de tokens
Dentre os vários aspectos que envolvem a implementação de soluções que dependam de APIs REST, serviços de rede ou recursos na nuvem, existe uma preocupação central a qualquer tipo de projeto: como tratar erros de uma maneira eficiente, dispensando a escrita de incontáveis linhas de código e permitindo à aplicação se recuperar quando da ocorrência de eventuais problemas?
Aplicações são consideradas resilientes quando apresentam a capacidade de se recuperar diante de falhas que não comprometem seu funcionamento básico. Diversos patterns podem ser empregados para a implementação de resiliência em um projeto de software: Retry, Circuit Breaker, Timeout e Fallback. A escolha da opção mais adequada dependerá de questões como o número de tentativas face a erros que venham a ocorrer, um tempo de espera entre tais tentativas etc.
O padrão Retry é talvez a solução mais simples entre os padrões mencionados, porém extremamente útil num grande número de cenários. Um bom exemplo disto seria a implementação de código para tratar a expiração de tokens de acesso em APIs REST:
- Como é de conhecimento geral, ao empregar JWT (JSON Web Tokens) estabelecemos um período de duração para os tokens de autenticação;
- Levando em conta uma aplicação que consuma várias vezes uma API, há a possibilidade de um token utilizado pela mesma expire entre alguns dos acessos. Diante disso, seria necessária a obtenção de um novo token e consequentemente uma nova tentativa de execução de uma operação específica.
E como podemos implementar código resiliente em aplicações .NET? A biblioteca Polly oferece uma boa resposta a esta pergunta, com a possibilidade de configuração de Policies para o tratamento e recuperação de falhas.
Neste artigo apresento em detalhes um exemplo de uso de Polly em .NET Core, através do consumo de uma API REST protegida por JWT.
E aproveito este espaço para deixar aqui ainda um convite.
Dia 20/08/2019 (terça) a partir das 21:30 — horário de Brasília — teremos mais uma live no Canal .NET. Desta vez farei uma apresentação em conjunto com o MVP André Secco, abordando a implementação de soluções serverless multiplataforma com .NET Core e Azure Functions.
Para efetuar a sua inscrição acesse a página do evento no Meetup. A transmissão acontecerá via YouTube, em um link a ser divulgado em breve.
Implementação do projeto
O projeto detalhado nesta seção foi criado com o .NET Core 2.2 e já está disponível no GitHub:
.NET Core 2.2 + HTTPClient + Consumo de API REST + JWT + Código Resiliente com Polly
Na imagem a seguir temos a biblioteca Polly adicionada a esta Console Application, com destaque para a compatibilidade com o .NET Standard:
Este exemplo é uma variação de outro projeto que publiquei anteriormente no GitHub:
.NET Core 2.2 + HTTPClient + Consumo de API REST + JWT
Na ocasião implementei uma classe chamada APIProdutoClient sem, contudo, proceder com qualquer forma de tratamento de falhas:
O tipo APIProdutoClient consome uma API REST para manipulação de um cadastro de produtos e que foi disponibilizada no seguinte link:
ASP.NET Core 2.2 + JWT (JSON Web Token) + Identity Core + Entity Framework Core InMemory + CORS
Para efeitos de testes o tempo de expiração dos tokens nesta API foi configurado como 30 segundos no arquivo appsettings.json (propriedade Seconds em TokenConfigurations):
Na classe RetryPolicyExtensions do projeto que fará uso da biblioteca Polly foi definido um Extension Method chamado ExecuteWithToken:
- Esta operação estende a classe RetryPolicy (namespace Polly.Retry), recebendo como parâmetros um objeto da classe Token contendo o token de autenticação e um delegate que fará uso dos tipos Context (namespace Polly) e HttpResponseMessage (namespace System.Net.Http);
- O delegate representado pelo parâmetro action será executado por meio do método Execute de um objeto do tipo RetryPolicy, o qual também receberá um dicionário contendo o token de acesso.
Na nova versão da classe APIProdutoClient foram implementados:
- O método Autenticar, empregado na obtenção do token de acesso para interação com a API de produtos;
- A propriedade IsAuthenticatedUsingToken indicará se ocorreu ou não a autenticação (existência de um token válido);
- Já o método CreateAccessTokenPolicy criará uma instância do tipo RetryPolicy, que estará vinculada à referência _jwtPolicy. Para isto serão acionados os métodos HandleResult (no qual foi especificada uma condição para tratamento de acesso não autorizado) e Retry (acionado mediante uma falha, com o limite de uma nova tentativa e repetindo o processo de autenticação para a obtenção de um novo token) da classe Policy (namespace Polly);
- Os métodos IncluirProduto e ListarProdutos farão uso do objeto vinculado a _jwtPolicy, com chamadas a ExecuteWithToken contendo o código a ser executado para interação com a API REST. Eventuais falhas resultarão em uma nova tentativa de execução para o código que está nos delegates informados como parâmetro a ExecuteWithToken (antes disso as instruções para se conseguir um novo token para o objeto do tipo RetryPolicy terão sido executadas).
Por fim, temos a definição da classe Program:
- Uma instância de APIProdutoClient será gerada, recebendo como parâmetros um objeto do tipo HttpClient (namespace System.Net.Http) e o objeto com as configurações da aplicação;
- Um primeiro token será obtido acionando o método Autenticar de APIProdutoClient;
- O método Interromper será invocado entre as várias chamadas à API REST empregada nos testes (operações IncluirProduto e ListarProdutos), como um meio para forçar situações que envolvam a expiração de um token.
Testes
Na próxima imagem é possível observar a geração de um primeiro token e o cadastramento de um produto:
Passado algum tempo que leve à expiração deste token será possível notar a execução da Policy baseada no pattern Retry, a obtenção de um novo token e a consequente repetição do processo envolvendo o cadastramento de um novo produto:
Este novo token será então empregado na consulta a produtos, com isto acontecendo dentro de um intervalo de tempo que não resulta em sua expiração:
E concluo este post deixando o link da live do Canal .NET em que o MVP Angelo Belchior demonstrou o uso de Polly em aplicações .NET. A gravação está disponível no YouTube e pode ser assistida gratuitamente: