ASP .NET Core -  Implementando e consumindo JSON Web Tokens - JWT


Neste artigo veremos como implementar a autenticação usando o padrão aberto JSON Web Tokens em uma Web API ASP .NET Core e como consumir os serviços em uma aplicação Console.

Eu já apresentei os conceitos e utilização do padrão JSON Web Tokens em duas vídeo aulas:

E, neste artigo vou mostrar novamente como implementar a autenticação via JWT em uma aplicação do tipo WEB API na ASP .NET Core, e a seguir como consumir os serviços expostos em uma aplicação Console.

Vamos implementar a autenticação usando JWT e para o login e o registro do usuário vamos usar o Identity e o EF Core.

Assim iremos criar uma classe de contexto ApplicationDbContext que herda de IdentityDbContext, e, uma classe ApplicationUser que herda de IdentityUser.

Vamos usar o EF Core para criar as tabelas com base no Identity apicando o migrations.

Criaremos o modelo de domínio para Usuario em uma classe UserInfo, e, para persistir os dados do token gerado vamos criar a classe UserToken.

'Bora' pra prática...

Recursos Usados:

Criando o projeto ASP .NET Core Web API

Abra o VS 2017 Community e clique em New Project;

Selecione o template ASP .NET Core Web Application e informe o nome WebApiUsuarios e clique em OK;

Selecione a seguir o template API para criar a Web API clique no botão OK.

Ao final teremos um projeto com uma estrutura padrão e um controlador ValuesController na pasta Controllers que iremos usar para testar a autenticação via JWT.

Definindo o modelo de domínio e o contexto

Nesta etapa vamos definir as classes de domínio UserInfo, ApplicationUser e UserToken e a classe de contexto ApplicationDbContext para realizar o mapeamento com o banco de dados usando o EF Core.

Vamos usar o Code-First e o Migrations para criar as tabelas para implementação do login e do registro do usuário.

Na pasta Models do projeto crie a classe UserInfo com o código a seguir:

        public class UserInfo
        {
          public string Email { get; set; }
          public string Password { get; set; }
        }

Na mesma pasta crie a classe UserToken que vai persistir o token JWT gerado e a data de expiração:

       public class UserToken
       {
           public string Token { get; set; }
           public DateTime Expiration { get; set; }
       }

Ainda na mesma pasta crie a classe ApplicationUser com o código a seguir:

using Microsoft.AspNetCore.Identity;

namespace WebApiUsuarios.Models
{
    public class ApplicationUser : IdentityUser
    { }
}

Agora crie uma pasta Context no projeto e a seguir nesta pasta vamos criar a classe ApplicationDbContext que representa o nosso contexto:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace WebApiUsuarios.Context
{
    public class ApplicationDbContext : IdentityDbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {
        }
    }
}
Registrando o contexto e definindo o configuração do Identity

Nesta etapa vamos registrar o nosso contexto e definir a configuração padrão do Identity no método ConfigureServices da classe Startup:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

No código acima, incluimos também os provedores padrão de tokens para gerar os tokens.

Precisamos agora incluir no arquivo appsettings.json a string de conexão do banco de dados CadastroDB que iremos usar neste exemplo:

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=MACORATTI;Initial Catalog=CadastroDB;Integrated Security=True;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Agora podemos aplicar o migrations executando o comando :

add-migration sistema_login

Na janela do gerenciador do console Nuget.

Será criada a pasta Migrations no projeto, e o arquivo de script  com os comandos para criar as tabelas. A seguir para executar o script e criar as tabelas vamos executar o comando :

update-database

Para criar as tabelas AspNetUsers dentre outras que iremos usar para armazenar informações do usuário.

Abaixo vemos as tabelas geradas no banco de dados CadastroDB :

Definindo o esquema de autenticação Bearer usando JWT

Agora podemos partir para a geração do Token onde vamos realizar as seguintes tarefas:

  1. Verificar as credenciais do usuário;
  2. Definir claims do usuário (nome, email, etc);
  3. Definir uma chave secreta e o algoritmo de encriptação usados;
  4. Gear o token com base no emissor, audiência, claims e definir a sua data de expiração

Para isso vamos criar o controlador UsuariosController na pasta Controllers e definir os métodos:

Neste código é importante destacar o namespace System.IdentityModel.Tokens.Jwt que fornece os recursos para geração do Token JWT:

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using WebApiUsuarios.Models;
namespace WebApiUsuarios.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UsuariosController : ControllerBase
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly SignInManager<ApplicationUser> _signInManager;
        private readonly IConfiguration _configuration;
        public UsuariosController(UserManager<ApplicationUser> userManager,
            SignInManager<ApplicationUser> signInManager,
            IConfiguration configuration)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _configuration = configuration;
        }
         [HttpGet]
         public ActionResult<string> Get()
         {
            return " << Controlador UsuariosController :: WebApiUsuarios >> ";
         }
        [HttpPost("Criar")]
        public async Task<ActionResult<UserToken>> CreateUser([FromBody] UserInfo model)
        {
            var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
            var result = await _userManager.CreateAsync(user, model.Password);
            if (result.Succeeded)
            {
                return BuildToken(model);
            }
            else
            {
                return BadRequest("Usuário ou senha inválidos");
            }
        }
        [HttpPost("Login")]
        public async Task<ActionResult<UserToken>> Login([FromBody] UserInfo userInfo)
        {
            var result = await _signInManager.PasswordSignInAsync(userInfo.Email, userInfo.Password, 
                 isPersistent: false, lockoutOnFailure: false);

            if (result.Succeeded)
            {
                return BuildToken(userInfo);
            }
            else
            {
                ModelState.AddModelError(string.Empty, "login inválido.");
                return BadRequest(ModelState);
            }
        }
        private UserToken BuildToken(UserInfo userInfo)
        {
            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.UniqueName, userInfo.Email),
                new Claim("meuValor", "oque voce quiser"),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
            };
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:key"]));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            // tempo de expiração do token: 1 hora
            var expiration = DateTime.UtcNow.AddHours(1);
            JwtSecurityToken token = new JwtSecurityToken(
               issuer: null,
               audience: null,
               claims: claims,
               expires: expiration,
               signingCredentials: creds);
            return new UserToken()
            {
                Token = new JwtSecurityTokenHandler().WriteToken(token),
                Expiration = expiration
            };
        }
    }
}

Ao fazer o login ou criar um usuário (via POST) com sucesso estamos gerando um Token com uma data de expiração que deverá ser usado para poder acessar os serviços da API que desejamos proteger usando o atributo Authorize.

No login após verificar as credenciais do usuário realizamos as seguintes operações:

Observe que ao gerar a chave usando a criptografia simétrica estamos obtendo uma chave JWT:key do arquivo de configuração appsettings.json.

Definimos também um método GET que apenas retornar uma string para verificar o funcionamento da API.

Precisamos então definir o valor desta chave secreta neste arquivo conforme abaixo:

{
  "JWT": {
    "Key" :  "afsdkjasjflxswafsdklk434orqiwup3457u-34oewir4irroqwiffv48mfs"
  },
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=MACORATTI;Initial Catalog=CadastroDB;Integrated Security=True;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

O valor definido na chave deve ser uma string complexa contendo diversos caracteres. Essa chave será usada para gerar e para validar o token.

Fazendo a validação do token na classe Startup

Após gerar o token ele será usado e enviado junto com toda a requisição para consumir o serviço.

Temos então que realizar a validação do token:  validar, o emissor, a audiência e validar a assinatura com a chave secreta.

Vamos abrir a classe Startup e incluir o código destacado em azul no método ConfigureServices:

  public void ConfigureServices(IServiceCollection services)
  {
        services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
         options.TokenValidationParameters = new TokenValidationParameters
         {
                 ValidateIssuer = false,
                 ValidateAudience = false,
                 ValidateLifetime = true,
                 ValidateIssuerSigningKey = true,
                 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["jwt:key"])),
                 ClockSkew = TimeSpan.Zero
               });
         }

Incluímos acima o código que vai validar o token. Usamos o método AddAuthentication() onde especificamos o tipo de autenticação usada : JwtBearerDefaults e a seguir realizamos algumas validações.

Observe que estamos usando a chave secreta (jwt:key) para validar a assinatura do token obtida do arquivo appsettings.json.

Agora já temos tudo pronto para gerar e validar os tokens JWT em nossa aplicação.

Vamos agora abrir o controlador ValuesController e incluir um atributo Authorize nos dois métodos GET do controlador conforme mostra a figura abaixo:

Agora para poder acessar esses métodos temos que realizar o login e obter o token e a seguir fazer requisição passando o token gerado no corpo da requisição.

Este cenário é representando pela figura abaixo onde é emitido um POST que cria um token JWT que será usado para as próximas requisições:

Então para testar vamos usar o Postman.

Usando o Postman - Gerando o Token e acessando a API

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

A primeira a coisa que devemos fazer é criar  um usuário na base de dados AspNetUsers, e, para isso vamos usar o método CreateUser do controlador UsuariosController informando o email e a senha no corpo de uma requisição POST usando o Postman.

Abrindo o Postman vamos definir a seguinte requisição para criar um usuário:

1 - https://localhost:44390/api/usuarios/criar

E no corpo da requisição (Body) usando o formato JSON (application/json) vamos passr as credencias:
{
   "email" : "teste@yahoo.com",
   "password" :  "SenhaSecreta#2019"
}

Conforme mostra a figura a abaixo:

Ao clicar no botão Send vemos o token gerado e a data de expiração atribuida.

Agora vamos fazer o login usando as credenciais que acabamos de criar para o usuário.

Para isso vamos copiar o token obtido e montar a seguinte requisição POST no Postman:

3- https://localhost:44390/api/usuarios/login

E no corpo da requisição (Body) usando o formato JSON (application/json) vamos passr as credencias:
{
   "email" : "teste@yahoo.com",
   "password" :  "SenhaSecreta#2019"
}

Conforme mostra a figura a abaixo:

Observe que após validar as credenciais é gerado o token para o usuário.

Agora já temos o token, e, vamos acessar a API ValuesController enviando uma requisição com o token.

Para isso vamos copiar o token obtido e montar a seguinte requisição GET no Postman:

3- https://localhost:44390/api/values

A seguir em Authorization marque a opção Bearer Token e informe o token conforme gerado figura abaixo:

Observe que obtivemos os valores retornados pelo método GET (que esta protegido com Authorize) indicando que o nosso token foi validado com sucesso.

Na próxima parte do artigo veremos como consumir essa API em uma aplicação console.

Pegue o projeto completo aqui: WebApiUsuarios.zip

"E disse Jesus: As raposas têm covis, e as aves do céu têm ninhos, mas o Filho do homem não tem onde reclinar a cabeça."
Mateus 8:20

Referências:


José Carlos Macoratti