A segurança de aplicações sempre foi, ou deveria ser, uma das principais
preocupações dos times de desenvolvimento, e nos últimos anos a autenticação de
APIs vem sendo bastante discutida nas comunidades técnicas em todo o mundo.
Existem diversas maneiras de resolver a autenticação em APIs, e uma delas é
fazendo uso de tokens de acesso, sendo o JWT (Json Web Token) um dos padrões de
tokens de acesso mais famosos e seguros da atualidade. Eu falei sobre ele em um
artigo anterior.
Hoje irei explicar os principais pontos da criação de uma API de autenticação
com JWT em ASP.Net Core. O código completo de exemplo encontra-se em meu
Github.
Lembrando que neste artigo irei focar em uma implementação simples, mas você
também pode usar outras soluções mais robustas para implementar autenticação,
como o Identity Server, que é open source e mantido
pela comunidade.
Conceitos
Antes de ver como funciona a criação de tokens JWT no ASP.Net Core vamos lembrar
dois conceitos fundamentais.
**Autenticação **→ é o ato de identificar que o usuário é quem ele diz ser,
validando suas credenciais de acesso que geralmente são um login e uma senha.
Também podem ser usados outros meios para identificar um usuário de um sistema,
como por exemplo usando biometria ou cartões de identificação;
**Autorização **→ é o ato de verificar se o usuário pode ou não ter acesso à um
recurso ou executar determinada ação dentro do sistema. Nesse ponto o usuário já
foi identificado (autenticado) previamente. Normalmente usamos Roles ou Policies
para autorizar o acesso à determinado recurso.
Hoje vamos focar somente na autenticação. Em um próximo artigo irei mostrar como
podemos fazer a autorização dos usuários em uma API ASP.Net Core.
De qualquer modo, tenha em mente esses dois conceitos pois eles são muito
importantes.
Como criar um token JWT
No projeto de exemplo existe uma classe chamada **JwtService **de modo à
simplificar a criação de um token JWT. Ela faz uso da classe
JwtSecurityTokenHandler (pacote nuget
System.IdentityModel.Tokens.Jwt)
que é quem realmente irá gerar um Json Web Token devidamente assinado e válido.
As informações contidas no token JWT são armazenadas em formato de Claims, sendo
assim, podemos usar o método GetClaimsIdentity passando uma instância de um
usuário préviamente *autenticado *através de suas credenciais de acesso para
obter a identificação do usuário. Esse método irá ler as propriedades do usuário
e criar um objeto ClaimsIdentity, que é armazenado na claim Subject
representando a identidade do usuário dentro do token.
Por segurança recomenda-se não armazenar informações confidenciais ou sensíveis
no token.
Perceba que durante a criação do token informamos mais alguns dados, chamados de
Reserved Claims segundo a especificação do JWT, que são atributos não
obrigatórios (mas recomendados) usados na validação do token pelos protocolos de
segurança das APIs.
Esses dados estão encapsulados em uma classe chamada JwtSettings, onde uma
instância de objeto é criada no momento em que a aplicação se inicia e
registrada no container de injeção de dependências. Os valores dela estão no
arquivo de configuração da aplicação, nosso querido appsettings.json.
Basicamente essas claims são:
- Issuer (iss) → quem emite o token JWT;
- Audience (aud) → aplicações que podem usar o token JWT, normalmente temos apenas
um valor, mas você pode informar mais de uma; - Expires (exp) → data e hora em que o token irá expirar;
- IssuedAt (iat) → data e hora em que o token foi emitido;
Você pode conferir a especificação do JWT
para obter mais detalhes sobre as claims.
De modo a garantir a segurança o token deve ser assinado digitalmente, sendo que
para isso normalmente usamos algoritmos como
HMAC ou
RSA.
Para assinar o token via RSA você precisa ter um certificado digital válido, já
para a assinatura HMAC é necessário apenas uma chave privada. Na aplicação de
exemplo estou usando HMAC.
A classe **JwtService **deverá ser utilizada por alguma classe de aplicação ou
até mesmo uma controller do MVC. Eu particularmente gosto muito de utilizar o
MediatR em meus projetos, sendo que já
falei sobre ele em um artigo
anterior.
Então no projeto de exemplo você encontrará a classe **AuthenticateUserHandler
**que será responsável por tratar/orquestrar um comando de login, que é
representado pela classe AuthenticateUser e que por sua vez, encapsula as
credenciais de acesso do usuário.
A classe **AuthenticateUserHandler **recebe em seu construtor dois parâmetros,
uma instância do repositório de usuários e uma instância do JwtService que
vimos anteriormente. Caso o usuário seja autenticado com sucesso pelo
repositório usamos o serviço para gerar um novo token JWT.
No projeto de exemplo eu uso a classe InMemoryDatabaseContext e uma lista de
User para representar o banco de dados e a tabela de usúarios, bem simples. O
foco deste artigo não é o repositório ou onde o dado está armazenado, mas em um
projeto real você deverá usar um banco de dados NoSql ou relacional.
Veja que também fazemos uso do padrão Notification
Pattern para retornar
mensagens de erro ao usuário, dessa forma não precisamos levantar Exceptions na
aplicação. Eu também falei sobre isso em um artigo
anterior.
Agora só precisamos recepcionar uma solicitação de login na API através de uma
Controller e encaminhar para o MediatR executar o processo de autenticação na
aplicação, dessa forma nossa Controller fica bem limpa.
Como validar o token JWT
Com um token JWT devidamente criado, ele deverá ser enviado via **Authorization
**header em todas as demais solicitações feitas à nossas APIs, sendo assim,
precisamos garantir que esse token esteja válido.
Para isso, o ASP.Net Core já possui um middleware responsável pela validação de
tokens de acesso.
Se você usa o meta-package Microsoft.AspNetCore.All então você já tem esse
middleware disponível e pode começar a usá-lo de imediato, caso contrário pode
instalar o pacote nuget
Microsoft.AspNetCore.Authentication.JwtBearer.
Essa configuração deverá ser feita em todas as APIs que irão receber o token
JWT, então você pode componentizar isso caso ache necessário.
Nesse método apenas estamos adicionando os middlewares necessários no injetor de
dependências nativo do ASP.Net Core e configurando alguns parâmetros que serão
verificados quando um token JWT for recepcionado pela API, como o Issuer,
Audience, assinatura e tempo de vida do token.
Além disso, você deve instruir o pipeline do ASP.Net Core a usar a Autenticação.
Isso deve ser feito no método Configure dentro da classe Startup da API.
Testando
Podemos usar o Postman para fazer as chamadas à
nossa API. Se você não quiser instalar o Postman não tem problema, a aplicação
de exemplo faz uso do Swagger para documentar e disponibilizar uma forma de
testar API de forma simples.
Primeiramente devemos registrar um usuário em nossa API informando seu nome,
e-mail e senha de acesso.
Criação de um novo usuário
Com o usuário criado, podemo usar o e-mail e senha para fazer a autenticação. Um
token JWT é retornado em caso de sucesso. Junto com o token informamos seu tipo
“bearer” e o tempo de expiração do mesmo.
Autenticação — retorna um token JWT
Caso as credenciais de acesso não estejam corretas, apenas retornamos uma
mensagem informando o problema, neste caso “Usuário ou senha inválidos”.
Nunca retorne exatamente qual foi o o motivo pelo qual a autenticação não foi
feita, por exemplo “Senha inválida”, pois isso já mostra para um possível
atacante que possivelmente o e-mail informado existe na base e então ele pode
apenas ficar testando as senhas para obter acesso indevido.
Usuário não autenticado
Com um token JWT válido podemos fazer a chamada à recursos restritos onde
somente usuários autenticados tem acesso. Nesse exemplo ele apenas retorna os
dados do próprio usuário.
Para isso você deve enviar o token no header Authorization da requisição
HTTP, usando a palavra “Bearer” como prefixo.
Authorization: Bearer [token_jwt]
Consulta o profile do usuário autenticado
Caso você tente consultar esse endpoint sem informar um token JWT válido, a API
irá retornar um HTTP Status Code 401 (Unauthorized) informando que você não está
autorizado à acessar esse recurso.
Usuário não está autenticado para visualizar seu profile
Conclusão
Como você pode ver a autenticação em APIs é realmente necessária hoje em dia.
Não podemos expor nossas APIs para o mundo sem garantir o mínimo de segurança, a
não ser que a intenção seja realmente deixá-la aberta.
Existem muitos outros pontos a observar, como uso de Refresh Tokens,
autenticação externa via Facebook, Google e Twitter por exemplo.
Este exemplo é uma implementação simples de uma API de autenticação, e conforme
eu disse anteriormente você pode utilizar uma solução mais robusta como o
Identity Server para usar em ambiente de produção.
Espero que tenham gostado e se ficou alguma dúvida, ou tenham críticas e
sugestões entrem em contato.
Abraços!
Referências