ASP.NET Core - Implementando Json Web Tokens(JWT) - I
Neste artigo veremos como implementar a autenticação JWT em aplicações ASP .NET Core. |
Embora a autenticação baseada em cookies ainda esteja disponível na ASP .NET Core, a autenticação JWT esta se tornando cada dia mais usada e conhecida.
Então o que é JWT ?
Antes de nos
aprofundarmos nos detalhes de implementação a nível de código, vamos entender
brevemente alguns conceitos relacionados ao JWT.
JWT significa JSON Web Tokens e, é um padrão aberto (RFC
7519) que define uma forma de passar dados do
cliente para o servidor. O JWT possui muitas vantagens em relação à autenticação
tradicional de cookies.
O JWT é mais seguro e também pode ser usado com clientes usam navegadores e também com aplicações mobile. A JWT é uma também opção preferencial para implementar a autenticação em aplicativos de página única (SPA).
Nota: As JWTs podem ser assinadas usando um segredo (com o algoritmo HMAC) ou um par de chaves pública/privada usando RSA ou ECDSA.
Um token JWT consiste em três partes:
Obs: O JWT possui palavras reservadas e recomendadas para serem colocadas dentro do payload. São elas:
- “iss” - O domínio da aplicação geradora do token
- “sub” - É o assunto do token, mas é muito utilizado para guarda o ID do usuário
- “aud” - Define quem pode usar o token
- “exp” - Data para expiração do token
- “nbf” - Define uma data para qual o token não pode ser aceito antes dela
- “iat” - Data de criação do token
- “jti” - O id do token
Juntando as 3 partes, a saída gera três strings Base64-URL separadas por pontos que podem ser facilmente transmitidos em ambientes HTML e HTTP, sendo mais compactos quando comparados a padrões baseados em XML, como o SAML.
Exemplo :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibm
FtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwp
MeJf36POk6yJV_adQssw5c
Você pode testar o JWT gerado no site : JWT.io
Fonte: https://jwt.io/introduction/
Ao contrário
dos cookies, que são passados automaticamente para o servidor, o JWT precisa
ser passado de forma explícita para o servidor. Então, um fluxo simplificado de
operações seria o seguinte:
1- O cliente envia credenciais de segurança, como nome de usuário e senha, para
o servidor para validação;
2- O servidor valida o nome de usuário e senha;
3- Se estiver correto, o servidor gera e emite um token JWT para o cliente;
4- O cliente recebe o token e o armazena em algum lugar;
5- Ao solicitar qualquer recurso ou ação do servidor, o cliente adiciona o token
JWT emitido anteriormente no cabeçalho
Authorization;
6- O servidor lê o cabeçalho de autorização para recuperar o token JWT;
7- Se o token for válido, o servidor executará a ação solicitada pelo cliente;
Então, basta colocar o JWT como um ticket. Se a solicitação recebida tiver um
ticket JWT, ela poderá acessar um recurso solicitado.
Etapas necessárias para implementar a autenticação baseada em JWT
Para
implementar a autenticação baseada em JWT, precisamos executar as seguintes
etapas:
1- Armazenar os detalhes do JWT em um arquivo de configuração;
2- Ativar o esquema de autenticação JWT na inicialização do aplicativo;
3- Criar algum mecanismo que valide o nome de usuário e a senha e emita um JWT;
4- Criar uma API protegida;
5- Invocar a API de um cliente;
Vamos executar estes passos um por um em um projeto ASP .NET Core.
Podemos usar o VS 2017 no Windows ou o VS code no Linux ou Mac (neste caso precisamos ter o .NET Core SDK instalado).
Criando uma aplicação ASP .NET Core - Implementando JWT
Abrindo o VS 2017 (atualizado para a versão 15.9.7) crie um novo projeto ASP .NET Core Web Application do tipo API (usando a versão 2.2 do .NET Core) com o nome AspCore_JWT:
Com o projeto criado vamos agora armazenar os detalhes do JWT em um arquivo de configuração.
Abra o arquivo appsettings.json e inclua uma seção chamada Jwt (o nome pode ser qualquer um) no arquivo conforme mostrado a seguir:
{
"Jwt": {
"Key": "aqui voce usa uma chaveSecreta para ser usada para assinar o token",
"Issuer": "AlgumIssuer",
"Audience": "AlgumaAudience"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
|
-
Key é uma string secreta que é usada para
assinatura do token;
- Issuer - Indica a parte que esta emitindo o JWT;
- Audience - Indica os destinatários do JWT;
E como funciona ?
Na autenticação, quando o usuário efetuar o login com êxito usando suas credenciais, um JSON Web Token será retornado caso as credenciais sejam validadas.
Obs: Como
os tokens são credenciais, muito cuidado deve ser
tomado para evitar problemas de segurança. Em geral, você não deve manter os
tokens por mais tempo do que o necessário.
Após isso, sempre que o usuário quiser acessar uma rota ou um recurso protegido,
deverá enviar o token JWT no cabeçalho
Authorization, usando o esquema do Bearer.
Habilitando o esquema da autenticação JWT no Startup
Vamos abrir a classe Startup e alterar o código do método ConfigureServices() habilitando o JWT:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication
(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
}
|
Invocamos o middleware AddAuthentication() e especificamos o esquema do portador JWT para ser o esquema de autenticação. Também especificamos várias opções para o esquema do portador(bearer) JWT.
Se você observar o objeto TokenValidationParameters, verá que ele indica se o emissor, a audiência, a vida útil e a chave de assinatura devem ser validados ou não. No exemplo usamos definindo como true.
Além disso, também especificamos um emissor válido, um público-alvo válido e uma chave de assinatura válida. Esses valores são recuperados do arquivo de configuração appsettings.json.
A seguir vamos incluir o código abaixo em azul, no método Configure da mesma classe:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseHttpsRedirection(); app.UseAuthentication();
app.UseMvc();
|
No código chamamos UseAuthentication() para conectar o middleware de autenticação ao pipeline HTTP.
Vamos agora criar uma API que vai validar o usuário e emitir um token JWT.
Criando uma API que valida as credenciais e emite um JWT
Neste artigo vamos validar o nome e a senha de um usuário, e para isso vamos criar uma pasta Models no projeto e nesta pasta criar a classe Usuario que representa o nosso modelo:
public class Usuario
{
public string NomeUsuario { get; set; }
public string Senha { get; set; }
} |
Agora precisamos de algum mecanismo para validar o nome e a senha do usuário que neste exemplo será uma Web API.
Vamos criar um novo controlador usando o template API Controller - Empty, chamado SegurancaController na pasta Controllers. Este controlador terá dois métodos privados e uma Action pública.
O primeiro método privado é chamado GerarTokenJWT() e gera um token JWT após validar as credenciais do usuário usando o método ValidarUsuario(), e, isso é feito na Action Login().
Como vamos precisar acessar informações que foram definidas no arquivo de configuração appsettings.json vamos injetar uma instância de Configuration no construtor do controlador e para isso precisamos incluir no método ConfigureServices() da classe Startup para usar o suporte nativa da injeção de dependência:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSingleton<IConfiguration>(Configuration);
.......
}
|
Após isso podemos definir o código do controlador SegurancaController conforme a seguir:
using AspCore_JWT.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
namespace AspCore_JWT.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class SegurancaController : ControllerBase
{
private IConfiguration _config;
public SegurancaController(IConfiguration Configuration)
{
_config = Configuration;
}
[HttpPost]
public IActionResult Login([FromBody]Usuario loginDetalhes)
{
bool resultado = ValidarUsuario(loginDetalhes);
if (resultado)
{
var tokenString = GerarTokenJWT();
return Ok(new { token = tokenString });
}
else
{
return Unauthorized();
}
}
private string GerarTokenJWT()
{
var issuer = _config["Jwt:Issuer"];
var audience = _config["Jwt:Audience"];
var expiry = DateTime.Now.AddMinutes(120);
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(issuer: issuer,audience: audience,
expires: DateTime.Now.AddMinutes(120),signingCredentials: credentials);
var tokenHandler = new JwtSecurityTokenHandler();
var stringToken = tokenHandler.WriteToken(token);
return stringToken;
}
private bool ValidarUsuario(Usuario loginDetalhes)
{
if (loginDetalhes.NomeUsuario == "Macoratti" && loginDetalhes.Senha == "Numsey$19")
{
return true;
}
else
{
return false;
}
}
}
}
|
Vamos entender o código :
1- No método GerarTokenJWT() recupera o emissor, o público-alvo e a chave do arquivo de configuração.
A seguir cria
uma nova SymmetricSecurityKey com base na chave. O
objeto SigningCredentials é gerado com base no
SymmetricSecurityKey. Observe que usamos o
algoritmo HS256 para gerar a assinatura
digital.
Agora podemos seguir em frente e criar um token JWT.
Isso é feito usando a classe JwtSecurityToken.
Passamos o emissor, o público-alvo, um DateTime de
expiração para o token e as credenciais de assinatura no construtor.
Queremos o JWT em um formato de string para que possa ser facilmente enviado ao
cliente. Isso é feito usando a classe
JwtSecurityTokenHandler. O método WriteToken()
aceita um JwtSecurityToken criado anteriormente e o
retorna como uma string de formato JSON compactada.
2- No método ValidarUsuario() valida se o nome e senha do usuário são válidos. Aqui estamos usando valores fixos no código para facilitar mas você pode usar o Identity ou outra técnica para validar o usuário.
Se as credenciais do usuário forem válidas, retornamos true, caso contrário, retornaremos false. Em vez de retornar true, você também pode retornar detalhes do usuário, como nome de usuário e outras informações. Aqui, por uma questão de simplicidade, não retornamos nenhum desses detalhes.
3 - Na Action Login usamos o atributo [HttpPost], ela será invocada pela aplicação cliente passando os detalhes do usuário : nome e senha.
Para fazer a
validação chamamos o método ValidarUsuario(), se as credenciais do usuário forem
válidas, chamamos o método GerarTokenJWT() para
gerar um token JWT. O token no formato string é retornado ao cliente com o status
HTTP
de Ok (código de status - 200).
Vamos testar a Action Login e nossa implementação usando o
Postman antes de
continuar.
Testando o Login com o Postman
Para testar nossa implementação vamos executar o projeto e usar o Postman.
Para instalar o Postman acesse esse link : https://www.getpostman.com/apps ou abra o Google Chrome e digite postam e a seguir clique no link: Postman - Chrome Web Store
Na janela que será aberta clique no botão - Usar no Chrome:
A seguir registre uma conta e faça o login.
Pronto ! Já podemos usar o Postman.
Para poder fazer o teste, como não temos uma interface com o usuário, vamos simular o envio dos dados via POST passando o nome do usuário(Macoratti) e a senha(Numsey$19) esperados.
Antes de executar você pode desabilitar o https na janela de propriedades da aplicação.
Execute o projeto no VS 2017, a seguir inicie o Postman e informe os seguintes dados:
Ao clicar no botão Send você terá como resultado o token JWT gerado e exibido no formato : header.payload.signature
Vamos conferir qual o conteúdo do nosso token gerado copiando o valor e navegando ate o o site jwt.io
Localize a seção Encoded em Debugger e copie e cole apenas o valor do token na caixa de texto Encoded.(1)
A seguir localize a seção Verify Signature e copie e cole sua Key do arquivo appsettings em 2. Você deve ver a forma decodificada do token como mostrado abaixo:
Observe a mensagem: Signature Verified indicando que o token gerado foi validado. Verifique o valor do Header e do Payload.
Muito bem, nossa implementação esta funcionando, e agora vamos continuar criando um API que necessita de segurança e vamos definir um acesso seguro a este recurso usando o token JWT gerado.
Faremos isso na próxima parte do artigo.
Pegue o projeto implementado aqui: AspCore_JWT.zip (sem as referências)
Ó Senhor,
Senhor nosso, quão admirável é o teu nome em toda a terra, pois puseste a tua
glória sobre os céus!
Salmos 8:1
Referências:
ASP .NET Core 2 - MiniCurso Básico
ASP .NET Core - Macoratti
Conceitos - .NET Framework versus .NET Core
ASP .NET Core - Conceitos Básicos
ASP .NET Core MVC - CRUD básico com ADO .NET
ASP .NET Core - Implementando a segurança com ...
ASP .NET Core - Apresentando Razor Pages
Minicurso ASP .NET Core 2.0 - Autenticação com JWT - I
Minicurso ASP .NET Core 2.0 - Autenticação com JWT - II