ASP.NET 5 – Autenticação e Autorização com Bearer e JWT

A autenticação JWT com Bearer token é a forma mais comum de proteger APIs no ASP.NET 5, e este guia mostra o processo completo, do login até a autorização por perfil. Você vai criar o projeto, gerar o token, configurar o pipeline e testar tudo com o Postman.

Além disso, o artigo explica o que existe dentro de um token e quais cuidados de segurança você precisa tomar antes de levar o código para produção.

Capa do tutorial de autenticação JWT e autorização com Bearer token no ASP.NET 5 e 6
Tutorial de autenticação e autorização com Bearer e token JWT no ASP.NET.

Se preferir acompanhar em vídeo, assista à versão gravada deste tutorial no YouTube.

Versão do ASP.NET 5 usada neste artigo

Este artigo usa a versão 5.0.0-rc.1 do ASP.NET e do .NET, que a Microsoft publicou em setembro de 2020. Ou seja, quando o texto foi escrito, a versão ainda não era o lançamento oficial.

Mesmo sendo pouco provável, ainda podiam ocorrer mudanças até a versão final. Por isso, confira sempre as notas de lançamento caso algum trecho se comporte de outro jeito na sua máquina.

Este conteúdo também atualiza uma versão anterior do mesmo tutorial, escrita para o ASP.NET Core 3. Na prática, a lógica continua a mesma, com pequenos ajustes de pacotes e de versão do SDK.

O que é autenticação JWT com Bearer

Na autenticação JWT, o servidor valida as credenciais do usuário uma única vez e devolve um token assinado. Em seguida, o cliente envia esse token em cada requisição, no cabeçalho Authorization, com o prefixo Bearer.

O nome Bearer significa portador. Em outras palavras, quem carrega o token tem acesso aos recursos que ele libera, então o token precisa ser tratado como uma credencial.

A grande vantagem é que a API não precisa guardar sessão. Assim, cada requisição traz tudo que o servidor precisa para saber quem é o usuário e qual é o seu perfil.

Estrutura de um token JWT

O formato JWT é definido pela RFC 7519 e tem três partes separadas por ponto. São elas o cabeçalho, o payload com as claims e a assinatura.

O cabeçalho informa o algoritmo de assinatura, como HMAC SHA256. Já o payload carrega as claims, por exemplo o nome do usuário, o perfil e a data de expiração.

A assinatura garante que ninguém alterou o conteúdo. Contudo, cabeçalho e payload são apenas codificados em Base64Url, então qualquer pessoa consegue ler o que está ali.

Por isso, nunca coloque senhas ou dados sensíveis dentro do token. O segredo do servidor protege a integridade, e não a confidencialidade.

Autenticação e autorização não são a mesma coisa

Autenticação responde à pergunta "quem é você?". Já a autorização responde "o que você pode fazer?".

No ASP.NET, o middleware de autenticação lê o token e preenche o usuário da requisição. Depois, o middleware de autorização confere se esse usuário tem permissão para acessar a rota.

Se quiser entender como esses componentes se encaixam no pipeline, o artigo sobre middleware no ASP.NET explica o conceito em detalhes.

Preparando o projeto para autenticação JWT

Para autenticar, precisamos de um usuário, uma senha e um perfil. Como o foco aqui não é acesso a dados, vamos fixar os usuários em código, dentro de um repositório.

O código que gera o token vai ficar em um serviço separado, que você pode reutilizar depois. Basta informar um usuário com perfil, e o serviço devolve o token, sem nenhuma dependência de banco de dados.

Para começar, crie a aplicação com os comandos abaixo:

dotnet new webapi -o Shop
cd Shop
dotnet restore
dotnet add package Microsoft.AspNetCore.Authentication
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Para executar este tutorial, você precisa do SDK do .NET 5 instalado. O pacote que importa de verdade é o Microsoft.AspNetCore.Authentication.JwtBearer, porque os recursos básicos de autenticação já fazem parte do framework compartilhado do ASP.NET Core.

Em seguida, abra o projeto no Visual Studio Code e remova os arquivos WeatherForecast gerados pelo template. Existe um na raiz e outro na pasta Controllers.

Modelo de usuário

Crie uma pasta chamada Models na raiz e, dentro dela, o arquivo User.cs:

namespace Shop.Models
{
    public class User
    {
        public int Id { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public string Role { get; set; }
    }
}

A propriedade Role guarda o perfil do usuário. Mais adiante, ela vai virar uma claim dentro do token e decidir quais rotas ficam liberadas.

Repositório de usuários

Agora crie a pasta Repositories na raiz e, dentro dela, o arquivo UserRepository.cs:

using System.Collections.Generic;
using System.Linq;
using Shop.Models;

namespace Shop.Repositories
{
    public static class UserRepository
    {
        public static User Get(string username, string password)
        {
            // Usuários fixos apenas para fins de estudo
            var users = new List<User>();
            users.Add(new User { Id = 1, Username = "batman", Password = "batman", Role = "manager" });
            users.Add(new User { Id = 2, Username = "robin", Password = "robin", Role = "employee" });

            // Compara o usuário sem diferenciar maiúsculas e a senha exatamente
            return users
                .Where(x => x.Username.ToLower() == username.ToLower() && x.Password == password)
                .FirstOrDefault();
        }
    }
}

Repare que a comparação de senha usa o parâmetro password. Na versão original do exemplo, a expressão comparava x.Password == x.Password, o que aceitava qualquer senha, então vale corrigir esse detalhe.

Para testar a autenticação JWT, o repositório tem dois usuários: o Batman com perfil manager e o Robin com perfil employee. Assim, conseguimos testar regras diferentes de autorização.

Chave privada da aplicação

Para assinar o token, precisamos de um segredo que só o servidor conhece, a chave privada. Neste cenário, crie o arquivo Settings.cs na raiz da aplicação:

namespace Shop
{
    public static class Settings
    {
        // Chave usada para assinar e validar os tokens
        public static string Secret = "fedaf7d8863b48e197b9287d492b708e";
    }
}

Esse arquivo serve apenas para expor a chave. Guardar o valor nas configurações da aplicação é ainda melhor, mas a classe estática deixa o exemplo mais simples.

Se quiser seguir pelo caminho das configurações desde já, adicione a chave ao appsettings.json e leia o valor pelo IConfiguration, que o Startup já recebe no construtor:

// appsettings.json: { "Jwt": { "Key": "your-long-random-secret" } }
var key = Encoding.ASCII.GetBytes(Configuration["Jwt:Key"]);

Dessa forma, cada ambiente usa sua própria chave na autenticação JWT, sem expor o segredo no repositório. O restante do tutorial segue com a classe Settings para manter o foco no fluxo.

Gerando o token JWT no ASP.NET 5

Na autenticação JWT, o pacote JwtBearer traz a classe JwtSecurityTokenHandler, que gera um token a partir das informações que fornecemos. Essas informações ficam em um SecurityTokenDescriptor.

O descritor define, entre outras opções, o usuário e o perfil. Com isso, esses dados também ficam disponíveis no ClaimsIdentity do ASP.NET.

Ou seja, o handler gera o token e ainda deixa o usuário acessível via User.Identity.Name nos controllers. Embora dê para gerar o token dentro de um controller, é melhor isolar essa lógica em um serviço.

Crie a pasta Services na raiz com o arquivo TokenService.cs:

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Shop.Models;
using Microsoft.IdentityModel.Tokens;

namespace Shop.Services
{
    public static class TokenService
    {
        public static string GenerateToken(User user)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(Settings.Secret);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                // Claims com o nome e o perfil do usuário
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(ClaimTypes.Name, user.Username.ToString()),
                    new Claim(ClaimTypes.Role, user.Role.ToString())
                }),
                // O token expira em duas horas
                Expires = DateTime.UtcNow.AddHours(2),
                SigningCredentials = new SigningCredentials(
                    new SymmetricSecurityKey(key),
                    SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }
    }
}

Acredite ou não, isso é tudo que precisamos para gerar o token da autenticação JWT no ASP.NET 5. O método WriteToken devolve a string final, pronta para o cliente.

Note que a chave do Settings.cs vira uma SymmetricSecurityKey, usada na assinatura HMAC SHA256. Portanto, só quem tem essa chave consegue produzir uma assinatura válida, e ela só existe no servidor.

Configurando a autenticação JWT no Startup

Até aqui, temos o usuário, o repositório e um serviço que gera o token com as claims do usuário. Agora precisamos avisar a aplicação que vamos usar autenticação JWT e autorização.

Dessa forma, o ASP.NET valida o token em cada requisição. Além disso, conseguimos definir quais perfis acessam cada ação dos controllers.

AddAuthentication e AddJwtBearer

O primeiro passo é chamar AddAuthentication no método ConfigureServices do Startup.cs:

services.AddAuthentication(x =>
{
    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
});

Aqui definimos o esquema padrão de autenticação e o esquema de challenge. Nesse caso, os dois usam o padrão do JwtBearer.

Depois, configuramos o JwtBearer com AddJwtBearer, ainda no ConfigureServices. O método completo fica assim:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors();
    services.AddControllers();

    var key = Encoding.ASCII.GetBytes(Settings.Secret);
    services.AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(x =>
    {
        // Em produção, mantenha HTTPS obrigatório
        x.RequireHttpsMetadata = false;
        x.SaveToken = true;
        x.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    });
}

O TokenValidationParameters diz como validar cada token recebido. Neste exemplo, o servidor confere a assinatura com a mesma chave, enquanto emissor e audiência ficam sem validação para simplificar.

A opção SaveToken guarda o token original nas propriedades da autenticação depois da validação. Assim, você consegue recuperar a string do token mais tarde, caso precise repassá-lo para outro serviço.

Como a autenticação JWT valida cada requisição

Vale entender o que acontece por baixo dos panos. Quando uma requisição chega, o handler do JwtBearer procura o cabeçalho Authorization e extrai o valor depois da palavra Bearer.

Em seguida, ele confere a assinatura com a chave configurada e verifica a data de expiração. Se tudo estiver certo, a autenticação JWT monta um ClaimsPrincipal com as claims do token e o associa à requisição.

Se o token faltar, estiver expirado ou tiver a assinatura alterada, o usuário continua anônimo. Nesse caso, uma rota protegida responde com 401, porque o esquema de challenge padrão é o próprio JwtBearer.

Repare que o servidor não consulta banco de dados nesse processo. Por isso, a autenticação JWT escala bem, já que qualquer instância da API valida o token sozinha, desde que conheça a chave.

UseAuthentication e UseAuthorization

Para fechar, ligamos os middlewares no método Configure:

app.UseAuthentication();
app.UseAuthorization();

A ordem importa, porque a autenticação precisa identificar o usuário antes de a autorização avaliar as permissões. Como resultado, o Startup.cs completo fica assim:

using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;

namespace Shop
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors();
            services.AddControllers();

            var key = Encoding.ASCII.GetBytes(Settings.Secret);
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false
                };
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseCors(x => x
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());

            // Primeiro identifica o usuário, depois avalia as permissões
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

Isso é tudo que o ASP.NET precisa saber sobre a nossa autenticação JWT e a autorização.

Endpoint de login com autenticação JWT

Para o exemplo, crie o arquivo HomeController.cs na pasta Controllers. Toda a autenticação JWT e a autorização vão ficar concentradas nele.

O método de login fica assim:

[HttpPost]
[Route("login")]
[AllowAnonymous]
public ActionResult<dynamic> Authenticate([FromBody] User model)
{
    // Recupera o usuário
    var user = UserRepository.Get(model.Username, model.Password);

    // Verifica se o usuário existe
    if (user == null)
        return NotFound(new { message = "Invalid username or password" });

    // Gera o token
    var token = TokenService.GenerateToken(user);

    // Oculta a senha antes de devolver o usuário
    user.Password = "";

    // Retorna os dados
    return new
    {
        user = user,
        token = token
    };
}

O exemplo original declarava o método como async Task, mas ele não usa await. Por isso, a versão síncrona evita o aviso do compilador sem mudar o comportamento.

Rode a API com dotnet watch run. Em seguida, faça um POST no Postman para https://localhost:5001/v1/account/login com este JSON no corpo:

{ "username": "robin", "password": "robin" }

Como resultado, a API devolve o usuário Robin e um token com seus dados e perfil. A partir daí, você usa esse token nas próximas requisições.

Autorização por perfis após a autenticação JWT

Para deixar o teste completo, vamos criar mais quatro métodos no HomeController. Um aceita qualquer usuário, outro exige autenticação e os dois últimos liberam acesso só para funcionários e gerentes.

Essas regras dependem de três atributos:

  • AllowAnonymous permite acesso sem nenhuma autenticação.
  • Authorize exige um usuário autenticado, sem olhar o perfil.
  • Authorize(Roles = "role1,role2") exige autenticação e um dos perfis listados, separados por vírgula.

O controller completo fica assim:

using System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Shop.Models;
using Shop.Repositories;
using Shop.Services;

namespace Shop.Controllers
{
    [Route("v1/account")]
    public class HomeController : Controller
    {
        [HttpPost]
        [Route("login")]
        [AllowAnonymous]
        public ActionResult<dynamic> Authenticate([FromBody] User model)
        {
            var user = UserRepository.Get(model.Username, model.Password);

            if (user == null)
                return NotFound(new { message = "Invalid username or password" });

            var token = TokenService.GenerateToken(user);
            user.Password = "";
            return new
            {
                user = user,
                token = token
            };
        }

        [HttpGet]
        [Route("anonymous")]
        [AllowAnonymous]
        public string Anonymous() => "Anonymous";

        [HttpGet]
        [Route("authenticated")]
        [Authorize]
        public string Authenticated() => String.Format("Authenticated - {0}", User.Identity.Name);

        [HttpGet]
        [Route("employee")]
        [Authorize(Roles = "employee,manager")]
        public string Employee() => "Employee";

        [HttpGet]
        [Route("manager")]
        [Authorize(Roles = "manager")]
        public string Manager() => "Manager";
    }
}

Repare que, na autenticação JWT deste exemplo, o perfil manager também acessa a rota employee. Assim, o gerente herda as permissões do funcionário sem nenhuma regra extra.

Testando a autenticação JWT com o Postman

Salve o arquivo e faça quatro requisições GET no Postman:

GET https://localhost:5001/v1/account/anonymous
GET https://localhost:5001/v1/account/authenticated
GET https://localhost:5001/v1/account/employee
GET https://localhost:5001/v1/account/manager

Nas rotas protegidas, informe o token na aba Headers, neste formato:

Authorization: Bearer YOUR_TOKEN

Com o token do Robin, as três primeiras rotas respondem normalmente, enquanto a rota manager devolve 403. Já sem token, as rotas protegidas devolvem 401, porque a autenticação JWT não identificou nenhum usuário.

Usuário logado após a autenticação JWT

No método Authenticated, recuperamos o nome do usuário logado com User.Identity.Name. O ASP.NET preenche essa propriedade sempre que um token válido chega no cabeçalho da requisição.

Além disso, o ClaimsPrincipal oferece métodos como User.IsInRole. Esses valores vêm das claims que definimos no TokenService.

Cuidados de segurança com autenticação JWT

O exemplo foi simplificado para fins didáticos. Antes de levar a autenticação JWT para produção, revise os pontos abaixo.

Primeiro, tire a chave do código-fonte. Guarde o segredo nas configurações, em variáveis de ambiente ou em um cofre de segredos, e use uma chave longa e aleatória.

Em seguida, valide emissor, audiência, expiração e assinatura. A documentação de JWT bearer da Microsoft recomenda validar todos esses itens em uma API.

Também mantenha RequireHttpsMetadata como true fora do ambiente de desenvolvimento. Afinal, um token trafegando sem HTTPS pode ser capturado e reutilizado por quem o interceptar.

Por fim, nunca guarde senhas em texto puro, como faz o repositório de exemplo. Em uma aplicação real, armazene apenas o hash da senha.

Para uma lista mais ampla de práticas, vale ler o guia de segurança em APIs ASP.NET Core.

Conclusão

Com poucas classes, você implementou autenticação JWT no ASP.NET 5: um repositório de usuários, um serviço que gera o token e a configuração do JwtBearer no Startup. Depois, protegeu rotas com Authorize e perfis.

Vale reforçar o fluxo em poucas palavras. O cliente envia usuário e senha uma vez, recebe o token e passa a enviá-lo no cabeçalho Authorization em todas as chamadas seguintes.

Do lado do servidor, o JwtBearer valida a assinatura e a expiração, monta o usuário com as claims e entrega a decisão final aos atributos Authorize. Assim, cada peça tem uma responsabilidade clara e fácil de testar.

O mesmo padrão serve de base para cenários maiores, com banco de dados, refresh tokens e provedores de identidade externos. Portanto, domine este fluxo antes de partir para soluções mais complexas.

FAQ

Qual a diferença entre erro 401 e 403 na autenticação JWT?

O 401 indica que a API não conseguiu identificar o usuário, por exemplo sem token ou com token inválido. Já o 403 indica que o usuário foi identificado, mas não tem o perfil exigido.

O token JWT é criptografado?

Não. O token é assinado, então o servidor detecta alterações, mas o payload pode ser lido por qualquer pessoa.

Por quanto tempo o token gerado neste exemplo vale?

O TokenService define a expiração com DateTime.UtcNow.AddHours(2), ou seja, duas horas após a emissão.

Posso exigir mais de um perfil em uma rota?

Sim. Basta listar os perfis separados por vírgula em Authorize(Roles = "employee,manager"), e qualquer um deles libera o acesso.

Preciso do pacote Microsoft.AspNetCore.Authentication no .NET 5?

O pacote essencial é o Microsoft.AspNetCore.Authentication.JwtBearer, porque a base de autenticação já vem no framework compartilhado.