Back-End

17 abr, 2019

ASP .NET Core – Implementando e consumindo JSON Web Tokens: JWT

Publicidade

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.

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

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

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

Usaremos o EF Core para criar as tabelas com base no Identity aplicando 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.

Vamos para a prática!

Recursos usados:

Criando o projeto ASP .NET Core Web API

Abra o VS 2017 Community, clique em New Project, selecione o template ASP .NET Core Web Application, informe o nome WebApiUsuarios e clique em OK.

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

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

Definindo o modelo de domínio e o contexto

Nesta etapa definiremos 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.

Usaremos 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, criaremos 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 a 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 incluímos 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 usaremos 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 abaixo:

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 usaremos 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:

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

Para isso criaremos o controlador UsuariosController na pasta Controllers e definiremos os métodos:

  • CreateUser: cria o usuário e gera o Token
  • Login: verifica as credencias e gera o Token
  • BuildToken: gera o Token

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:

  • Definimos as claims – o tipo e o valor
  • Definir a chave usando a SymmetricSecutiryKey usando a chave secreta
  • Definir as credenciais
  • Gerando o token
  • 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 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.

Então temos 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.

Agora vamos abrir o controlador ValuesController e incluir um atributo Authorize nos dois métodos GET do controlador, conforme a figura abaixo:

Agora, para poder acessar esses métodos, temos que realizar o login, 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 este link ou abra o Google Chrome e digite postman e a seguir clique no link: Postman – Chrome Web Store

A primeira 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 e-mail e a senha no corpo de uma requisição POST usando o Postman.

Abrindo o Postman definiremos 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 passar 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 atribuída.

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:

2 – 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"
}

Como na figura a abaixo também:

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 na figura abaixo:

Observe que obtivemos os valores retornados pelo método GET (que está 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.