.NET Cross PlatformDesenvolvimentoSolutions & Design Gallery

Após o hangout que rolou nessa sexta estávamos discutindo JWT no ASP.NET Core (JSon Web Tokens) e ao apresentar um dos meus projetos cheguei a ficar envergonhado, pois eu havia dado uma certa volta para evitar a utilização de criptografia simétrica e acabei fazendo uma implementação de ISecurityTokenValidator o que é uma imensa volta para uma implementação padrão de geração tokens JWT. Bom, madrugada livre, resolvi acertar isso de uma vez e acabei transformando esse aprendizado em post.

A utilização de um JWT independente se dá porque não tenho uma autoridade externa no meu projeto, não tenho SSO, não tenho Identity, sequer OpenID ou logins sociais, tenho uma simples autenticação, baseada em hash, claro. O projeto consiste em uma aplicação standalone, geralmente utilizada por um ou dois usuário no máximo e para simplificar o deploy sequer tenho um banco de dados. A função da webapp é ser um utilitário de gestão. Assim, subir um gestor de identidade é too much para a necessidade do projeto e não soa tão bem. Após a vergonha alheia, resolvi fazer da forma certa, e nada melhor do que aproveitar a release do .NET Core 2.0 para entender, estudar e escrever sobre o tema. Espero em breve falar de JWT com WSO2 Identity Server, que é um tema legal também, mas por enquanto, vamos à implementação standalone, assim é plausível apresentar os requisitos para a implementação de JWT de forma mais clara, já que encontramos de tudo no nosso oráculo contemporâneo.

O JWT consiste em json estrutruado em Header e Payload assinado. Seus cabeçalhos são pertinentes ao protocolo e no payload você utiliza para colocar dados adicionais que ficam sob a sua escolha. Quais dados? Quem gera esses tokens é responsável por defini-los, é bem flexível, geralmente são adicionadas claims que definem características quaisquer dos seus usuários. Podem ser dados simples, como nome de usuário, friendly names, até roles e atributos. Como são assinados? Criptografia simétrica, chave pública e privada, certificados, há algumas muitas implementações possíveis. Todo esse entendimento é facilmente apresentado no jwt.io, aproveitei para tirar um print e mostrar de forma mais didática no que consiste um JWT.

Todos os pontos são relevantes, então na esquerda temos o token “encodado”, e quebrado em 3 elementos: Header, Payload e Assinatura. A assinatura garante a autenticidade do documento e esse é um desenho muito interessante para inclusive outras aplicações.

Sobre JWT no ASP.NET Core, há regras a serem consideradas:

  1. Durante a autenticação você precisa gerar o token e devolver para seu cliente. Pois esse token precisa ser enviado a cada requisição autenticada. A propósito, a geração da string que representa o JWT é um processo bem definido na plataforma e utiliza classes específicas como JwtSecurityToken e JwtSecurityTokenHandler.
  2. Actions ou Controllers inteiros que precisam de autorização, dependem do atributo [Authorize], independente de você da existência de uma policy adicional ou default.
  3. A propósito, é necessário definir uma política (policy), explícita ou padrão, mas esta só é utilizada quando o Controller ou Action está marcado com o atributo Authorize, o construtor desse atributo permite a utilização de um nome de política, há um exemplo aqui nos códigos.

#showMeTheCode

Autenticação

Alguns aspectos precisam ser considerados nessa implementação. Na linha 11 eu estou usando uma biblioteca chamada BCrypt para gestão de hash de senha (por si só é um tema interessante para entender e estudar). Nas linhas 14, 15, 21 e 22 a instância jwtConfiguration é usada na configuração de diversos parâmetros necessários para a configuração do ASP.NET Core. Esse objeto foi preenchido a partir da configuração da aplicação e devidamente setado na infra de injeção de dependência. Atenção, nenhum desses parâmetros deve ser null ou empty! Nas linhas 14 e 15 temos as definições de Issuer e Audience, esses são elementos importantíssimos para o fluxo, leve em conta o parágrafo anterior para que não sofra com pequenos erros omitidos pela infraestrutura. De qualquer forma vou falar sobre troubleshooting neste post ainda. Na linha 24 temos o método WriteToken da classe JwtSecurityTokenHandler sendo chamado para gerar o token que será enviado para o navegador. Não ignore isso, pois a infraestrutura do ASP.NET Core precisará validar a assinatura do seu token, a cada request, portanto, esse fluo é responsabilidade da infra do asp.net core, e gerar esse token com algoritmos próprios fará você fugir do protocolo, como estava acontecendo com a minha implementação anterior.

Configuração

Acima temos a configuração final, utilizada dentro do método ConfigureServices da classe Startup. Assim como no exemplo anterior, o objeto jwtConfiguration está populado com as configurações do meu appsettings.json.

Nesse exemplo estou usando uma política default, já que essa minha aplicação não precisa diferenciar políticas de autorização, ou faz-se tudo ou não faz nada. Mas você poderia trabalhar com políticas diversas, e assim flexibilizar o modelo de autorização de sua API. Vale a pena lembrar que para chegar até aqui foi necessário algum troubleshooting, e isso não é transparente pois a não ser que você faça alguma coisa muito grotesca, exceptions não serão lançadas, e as que serão, não pararão a execução, serão logadas no console e você ficará a ver navios, portanto vou à dica de 2 dolares: A imagem acima apresenta a classe JwtBearerEvents que você pode instanciar e atribuir a jwtBearerOptions.Events para interceptar os eventos do fluxo de JwtBarear. Eu ignorei o evento OnMessageReceived pois ele é repetitivo e não traz nenhum grande valor no troubleshooting, os outros 3 (OnAuthenticationFailedOnChallengeOnTokenValidated) te ajudam a entender o que está acontecendo. Como você pode ver no console, uma exception foi lançada e tratada. Nesse caso a exception acontece em virtude do token expirado, esse é um cenário comum, e não tem relação com erros de configuração, no entanto o fluxo é exatamente o mesmo quando há erros de configuração, portanto, enquanto implementa sua configuração de JWT é muito interessante manter esse código e esses breakepoints para que possa inspecionar cada um dos contextos e entender o que está errado..

Eu, para demonstrar um erro de configuração, troquei os valores das linhas 16 e 17 de ValidIssuer = jwtConfiguration.Issuer, ValidAudience = jwtConfiguration.Audience para ValidIssuer = string.Empty, ValidAudience = string.Empty, e como disse, misteriosamente não temos uma exception lançada, como esperado, temos apenas o log de uma. A infraestrutura simplesmente ignora o erro, logando-o, e não deixa a autenticação proceder. Para alívio o evento OnAuthenticationFailed é chamado sempre que ocorrer um erro na autenticação, com a inspeção do contexto é possível entender qual exception foi lançada e assim corrigir sua configuração. Vale lembrar que esse post é complementar e aborda um fluxo de autenticação local, com jwt. Existem algumas outras preocupações quando você está trabalhando com o Identity, por exemplo, facebook e outros. A forma de trabalhar ligeiramente diferente e você precisa estar mais compromissado com os valores de Issuer, Audience e Authority

Autorizando sua API

Antes que eu me esqueça, abaixo estão 3 modelos de utilização do Authorize. Se você seguir o exemplo acima, apenas o primeiro funcionará, no entanto, caso você utilize/defina políticas e roles, os outros 2 exemplos funcionarão, respectivamente olhando para uma política ou role.

Outro ponto relevante é que, caso você não marque essa action com o atributo Authorize, o resultado de this.User.Identity.IsAuthenticated é falso! A infraestrutura de autenticação/autorização simplesmente é ignorada.

Adicionando o cabeçalho à requisição

O exemplo acima é utilizando em uma aplicação angular 4, no meu caso eu escolhi guardar o token no SessionStorage mas poderia guardar no LocalStorage ou fazer um mix dos dois. Por fim, você saberá que configurou tudo direito quando o breakpoint do evento OnTokenValidated for alcançado. A classe TokenValidatedContext, usada no contexto desse método possui a propriedade SecurityToken e contém tudo o que você precisa para entender seu JWT. Ao chegar nesse ponto, provavelmente não faltará mais nada, e as partir daí, aconselho apagar esse código de troubleshooting.

Abaixo seguem alguns links legais que apresentam outras implementações sob outros cenários:

JWT Validation and Authorization in ASP.NET Core

Angular Token Based Authentication using Asp.net Core Web API and JSON Web Token

Bom, é isso! Por hoje é só!

Comente, compartilhe, curta!

Logo abaixo desse texto você encontra os Posts Relacionados, e botões de compartilhamento, em seguida a sessão de comentários!

Gostou? Então aproveite para curtir, compartilhar e enviar comentários, dúvidas ou sugestões.

Conheça o Grupo Arquitetura de Softwate | .NET: Facebook e Telegram
Luiz Carlos Faria: Site, Youtube, Facebook, Twitter, Telegram, Linkedin e Email