JWT significa e é um mecanismo de autorização, não de autenticação. Então, vamos descobrir qual é a diferença entre os dois. JSON Web Token é o mecanismo que permite verificar se o usuário é exatamente quem afirma ser. É um processo de login em que um usuário fornece um nome de usuário e uma senha e o sistema os verifica. Portanto, a autenticação responde à pergunta: quem é o usuário? A autenticação é o mecanismo que permite verificar quais direitos de acesso o usuário possui a determinado recurso. É um processo de concessão aos usuários de algumas funções e um conjunto de permissões que uma determinada função possui. Então, a autorização responde a essa pergunta: quais direitos o usuário tem no sistema? Autorização É importante entender que a Autenticação sempre vem em primeiro lugar e a Autorização em segundo. Em outras palavras, você não pode obter permissão antes de verificar sua identidade. Mas quais são os métodos de autorização mais populares? Existem duas abordagens principais para lidar com a autorização do aplicativo da web. Sessões Uma abordagem tradicional na web para usuários autorizados é uma sessão do lado do servidor baseada em cookies. O processo começa quando um usuário faz login e um servidor o autentica. Depois disso, o servidor cria uma sessão com um ID de sessão e a armazena em algum lugar da memória do servidor. O servidor envia de volta o ID da sessão ao cliente e o cliente armazena o ID da sessão em cookies. Para cada solicitação, o cliente envia um ID de sessão como parte da solicitação, e o servidor verifica o ID de sessão em sua memória e as permissões do usuário relacionadas a esta sessão. Fichas Outra abordagem popular é usar tokens para autorização. O processo começa de forma semelhante quando um usuário insere login e senhas e um cliente envia uma solicitação de login a um servidor. Em vez de criar uma sessão, o servidor gera um token assinado com o token secreto. Em seguida, o servidor envia o token de volta ao cliente, e o cliente deve armazená-lo em um armazenamento local. Semelhante à abordagem baseada em sessão, o cliente deve enviar um token ao servidor para cada solicitação. Entretanto, o servidor não armazena nenhuma informação adicional sobre a sessão do usuário. O servidor deve validar se o token não mudou desde que foi criado e assinado com a chave secreta. Sessão vs Token A abordagem de autorização baseada em sessão pode ser vulnerável a um ataque conhecido como Cross-Site Request Forgery (CSRF). É um tipo de ataque quando o invasor aponta para um site no qual está logado para realizar ações que não pretendia, como enviar um pagamento ou alterar uma senha. Outra coisa é que, ao usar uma abordagem de autorização baseada em sessão, cria-se uma sessão com estado entre um cliente e um servidor. O problema é que se um cliente quiser acessar servidores diferentes no escopo da mesma aplicação, esses servidores terão que compartilhar um estado de sessão. Caso contrário, o cliente precisará estar autorizado em cada servidor, pois a sessão será diferente. Por outro lado, a abordagem de autorização baseada em token não requer o armazenamento de dados de sessão no lado do servidor e pode simplificar a autorização entre vários servidores. No entanto, os tokens ainda podem ser roubados por um invasor e também pode ser difícil invalidá-los. Veremos os detalhes e como lidar com a invalidação mais adiante neste artigo. JWT JSON Web Token (JWT) é um padrão aberto que define uma maneira compacta e independente de transmitir informações com segurança entre as partes como um objeto JSON. Essas informações podem ser verificadas e confiáveis porque são assinadas digitalmente. Os JWTs podem ser assinados usando um segredo (com o algoritmo ) ou um par de chaves pública/privada usando ou . HMAC RSA ECDSA Estrutura JWT JSON Web Tokens consistem em três partes separadas por pontos . Cabeçalho { "alg": "HS256", "typ": "JWT" } O cabeçalho geralmente consiste em duas partes: o tipo de token e o algoritmo de assinatura usado. Carga útil { "sub": "1234567890", "name": "John Doe", "admin": true } A carga útil contém as declarações, que são declarações sobre o usuário. A carga útil é então codificada em para formar a segunda parte do JSON Web Token. Você pode encontrar uma descrição dos campos padrão usados como declarações . Base64Url aqui Assinatura HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) Para criar a parte da assinatura, você deve pegar o cabeçalho codificado, a carga útil codificada, um segredo e o algoritmo especificado no cabeçalho e assiná-los. O token normalmente se parece com o seguinte: xxxxx.yyyyy.zzzzz Você pode navegar até e depurar um token de amostra ou o seu próprio. Basta colar seu token no campo e selecionar o da assinatura do token. jwt.io Codificado Algoritmo Projeto .NET Agora que temos conhecimento teórico de como o JWT funciona, podemos aplicá-lo ao projeto da vida real. Vamos supor que temos uma API simples que representa operações CRUD para a entidade coffee. Vamos criar um projeto de API ASP.NET Core que representa a API Coffee. Depois disso, criaremos outro projeto de API ASP.NET Core que representaria uma API de identidade que poderia gerar JWT. Na vida real, você provavelmente usaria ou ou para fins de autenticação/autorização. No entanto, criaríamos nossa própria API de identidade para demonstrar como gerar JWT. Quando a API de identidade estiver concluída, podemos chamar seu controlador e gerar JWT com base nos dados do usuário. Além disso, podemos proteger a API Coffee com uma configuração de autorização que requer a passagem de JWT em cada solicitação. Identity Server Okta Auth0 API de café Primeiro, vamos criar um projeto simples de API ASP.NET Core que representa a API Coffee. Aqui está a estrutura deste projeto: Vamos começar com na pasta . É uma entidade simples com propriedades e . Coffee.cs Model Id Name namespace Hackernoon.Coffee.API.Model; public class Coffee { public int Id { get; set; } public string Name { get; set; } } Precisamos armazenar nossas entidades enquanto trabalhamos com a API. Então, vamos apresentar um armazenamento simples na memória. Ele está localizado no arquivo na pasta . Storage.cs Data namespace Hackernoon.Coffee.API.Data; public static class Storage { private static readonly List<Model.Coffee> Data = new(); public static List<Model.Coffee> GetAll() { return Data; } public static bool Create(Model.Coffee model) { if (Data.Any(c => c.Id == model.Id || c.Name == model.Name)) return false; Data.Add(new Model.Coffee { Id = model.Id, Name = model.Name }); return true; } public static bool Delete(int id) { if (Data.All(c => c.Id != id)) return false; Data.Remove(Storage.Data.First(c => c.Id == id)); return true; } public static bool Update(Model.Coffee model) { if (Data.All(c => c.Id != model.Id)) return false; Data.First(c => c.Id == model.Id).Name = model.Name; return true; } } Precisamos de uma classe que represente solicitações para a API Coffee. Então, vamos criar na pasta . CoffeeRequest.cs Contracts namespace Hackernoon.Coffee.API.Contracts; public class CoffeeRequest { public int Id { get; set; } public string Name { get; set; } } Quando terminar, podemos implementar na pasta que representa as operações CRUD para a entidade coffee. CoffeeController.cs Controller using Hackernoon.Coffee.API.Contracts; using Hackernoon.Coffee.API.Data; using Microsoft.AspNetCore.Mvc; namespace Hackernoon.Coffee.API.Controllers; [Route("coffee")] [ApiController] public class CoffeeController : ControllerBase { [HttpGet] public IList<Model.Coffee> GetAll() { return Storage.GetAll(); } [HttpPost] public IActionResult Create([FromBody]CoffeeRequest request) { var model = new Model.Coffee { Id = request.Id, Name = request.Name }; if (!Storage.Create(model)) return new BadRequestResult(); return new OkResult(); } [HttpDelete] public IActionResult Delete(int id) { if (!Storage.Delete(id)) return new BadRequestResult(); return new OkResult(); } [HttpPut] public IActionResult Update([FromBody] CoffeeRequest request) { var model = new Model.Coffee() { Id = request.Id, Name = request.Name }; if (!Storage.Update(model)) return new BadRequestResult(); return new OkResult(); } } A API Coffee está pronta e podemos executar o projeto e ver a UI do Swagger da seguinte maneira: API de identidade Vamos criar outro projeto de API ASP.NET Core que representa a API Identity. Aqui está a estrutura deste projeto: Vamos começar pela pasta na pasta , que representa a solicitação de geração de um novo JWT com propriedades e . TokenGenerationRequest.cs Contracts Email Password namespace Hackernoon.Identity.API.Contracts; public class TokenGenerationRequest { public string Email { get; set; } public string Password { get; set; } } Precisamos implementar apenas que representa a lógica de geração do JWT. Mas antes de fazermos isso, o pacote NuGet precisa ser instalado. TokenController.cs Microsoft.AspNetCore.Authentication.JwtBearer using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Hackernoon.Identity.API.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; namespace Hackernoon.Identity.API.Controllers; [Route("token")] public class TokenController : ControllerBase { private const string SecretKey = "VerySecretAndLongKey-NeedMoreSymbolsHere-123"; private const string Issuer = "IdentityServerIssuer"; private const string Audience = "IdentityServerClient"; private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(20); [HttpPost] public string Create([FromBody]TokenGenerationRequest request) { var claims = new List<Claim> {new Claim(ClaimTypes.Email, request.Email) }; var jwt = new JwtSecurityToken( issuer: Issuer, audience: Audience, claims: claims, expires: DateTime.UtcNow.Add(Lifetime), signingCredentials: CreateSigningCredentials()); return new JwtSecurityTokenHandler().WriteToken(jwt); } private static SigningCredentials CreateSigningCredentials() { return new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)), SecurityAlgorithms.HmacSha256); } } Observe que const sensíveis como , e devem ser colocados em algum lugar da configuração. Eles são codificados apenas para simplificar este projeto de teste. O campo está definido para 20 minutos, o que significa que o token será válido durante esse período. Você também pode configurar esse parâmetro. SecretKey Issuer Audience Lifetime Agora podemos executar o projeto e ver a UI do Swagger da seguinte maneira: Vamos fazer uma chamada para o endpoint e gerar um novo JWT. Experimente a seguinte carga: /token { "email": "john.doe@gmail.com", "password": "password" } A API de identidade gerará o JWT correspondente: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJJc0dvdXJtZXQiOiJmYWxzZSIsImV4cCI6MTcwNzc4Mzk4MCwiaXNzIjoiSWRlbnRpdHlTZXJ2ZXJJc3N1ZXIiLCJhdWQiOiJJZGVudGl0eVNlcnZlckNsaWVudCJ9.4odXsbWak1C0uK3Ux-n7f58icYQQwlHjM54OjgMCVPM Habilitando autorização na API Coffee Agora, quando a API Identity estiver pronta e nos fornecer tokens, podemos proteger a API Coffee com autorização. Novamente, o pacote NuGet precisa ser instalado. Microsoft.AspNetCore.Authentication.JwtBearer Precisamos registrar os serviços necessários pelos serviços de autenticação. Adicione o código a seguir ao arquivo logo após criar um construtor. Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "IdentityServerIssuer", ValidateAudience = true, ValidAudience = "IdentityServerClient", ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes("VerySecretAndLongKey-NeedMoreSymbolsHere-123")), ValidateIssuerSigningKey = true, }; }); builder.Services.AddAuthorization(); É importante lembrar que a ordem no middleware é importante. Habilitamos a autenticação chamando o método e especificando como um esquema de autenticação. É uma constante que contém um valor . AddAuthentication() JwtBearerDefaults.AuthenticationScheme Bearer namespace Microsoft.AspNetCore.Authentication.JwtBearer { /// <summary>Default values used by bearer authentication.</summary> public static class JwtBearerDefaults { /// <summary> /// Default value for AuthenticationScheme property in the JwtBearerAuthenticationOptions /// </summary> public const string AuthenticationScheme = "Bearer"; } } Precisamos especificar que descreve quais parâmetros do JWT serão validados durante a autorização. Também especificamos semelhante ao na API Identity para verificar a assinatura JWT. Confira mais detalhes sobre . TokenValidationParameters IssuerSigningKey signingCredentials TokenValidationParameters aqui O próximo trecho de código adiciona middleware ao construtor que habilita recursos de autenticação e autorização. Deve ser adicionado entre os métodos e . UseHttpsRedirection() MapControllers() app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); Agora podemos usar o atributo sobre o controlador ou suas ações. Ao aplicar este código, agora todas as ações no ficam protegidas por um mecanismo de autorização, e o JWT deve ser enviado como parte da solicitação. Authorize CoffeeController [Route("coffee")] [ApiController] [Authorize] public class CoffeeController : ControllerBase { .. Se fizermos uma chamada para qualquer endpoint da API Coffee, podemos depurar e ver se ele está preenchido e tem uma com declarações que especificamos no JWT. É importante entender como o ASP.NET Core lida com a autorização nos bastidores. HttpContext.User Identity Adicionar autorização à IU do Swagger Fizemos um ótimo trabalho para proteger a Coffee API com a autorização. Mas se você executar o projeto Coffee API e abrir a UI do Swagger, não poderá enviar JWT como parte da solicitação. Para corrigir isso, precisamos atualizar o arquivo com o seguinte código: Program.cs builder.Services.AddSwaggerGen(option => { option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter a valid token", Name = "Authorization", Type = SecuritySchemeType.Http, BearerFormat = "JWT", Scheme = "Bearer" }); option.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type=ReferenceType.SecurityScheme, Id="Bearer" } }, new string[]{} } }); }); Depois disso, poderemos ver o botão no canto superior direito: Autorizar Ao clicar no botão você poderá inserir o JWT da seguinte forma: Autorizar Use o Postman para testes Você não pode se limitar a usar o Swagger UI e pode realizar testes da API através da ferramenta Postman. Vamos chamar o endpoint da API Identity primeiro. Precisamos especificar o cabeçalho com o valor na seção , pois usaremos JSON como carga útil. /token Content-Type application/json Headers Depois disso, podemos chamar o endpoint e obter um novo JWT. /token Agora podemos copiar o JWT e usá-lo para chamar a API Coffee. Precisamos especificar o cabeçalho semelhante à API Identity se quisermos testar, criar e atualizar endpoints. O cabeçalho também deve ser definido com o valor . Depois disso, basta clicar no botão e ver o resultado. Content-Type Authorization Bearer [your JWT value] Enviar Autorização baseada em função Como você se lembra, a parte da carga útil do JWT é um conjunto de declarações com valores que são exatamente pares de valores-chave. A autorização baseada em função permite diferenciar o acesso aos recursos do aplicativo dependendo da função à qual o usuário pertence. Se atualizarmos o método no arquivo na API Identity com o código que adiciona uma nova declaração para a função; podemos lidar com a autenticação baseada em funções na API Coffee. é um nome predefinido da declaração de função. Create() TokenController.cs ClaimTypes.Role var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim(ClaimTypes.Role, "Barista") }; Atualize o atributo no arquivo especificando o nome da função: Authorize CoffeeController.cs [Authorize(Roles = "Barista")] Agora, todos os usuários que fizerem uma chamada para a API Coffee deverão ter a reivindicação role com o valor . Caso contrário, eles receberão o código de status . Barista 403 Forbidden Autorização baseada em reivindicação Um atributo pode lidar facilmente com a autenticação baseada em função. Mas e se não for suficiente e quisermos diferenciar o acesso com base em algumas propriedades do usuário, como idade ou qualquer outra? Você provavelmente já adivinhou que pode adicionar suas declarações ao JWT e usá-las para construir lógica de autorização. A própria autorização baseada em função é um caso especial de autorização baseada em declarações, assim como uma função é o mesmo objeto de declaração de um tipo predefinido. Authorize Vamos atualizar o método no arquivo na API Identity com o código que adiciona uma nova declaração . Create() TokenController.cs IsGourmet var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim("IsGourmet", "true") }; No arquivo Program.cs da API Coffee, precisamos criar uma política que verifique uma reclamação e possa ser usada no atributo . O código a seguir deve ser adicionado logo após a chamada do método . Authorize AddAuthentication() builder.Services.AddAuthorization(opts => { opts.AddPolicy("OnlyForGourmet", policy => { policy.RequireClaim("IsGourmet", "true"); }); }); Atualize o atributo no arquivo especificando o nome da política: Authorize CoffeeController.cs [Authorize(Policy = "OnlyForGourmet")] Resumo Parabéns! Você fez um grande esforço para aprender JWT em .NET. Agora, você precisa ter um conhecimento sólido dos princípios do JWT e por que é importante usá-lo para realizar autorização em aplicativos .NET. Mas apenas arranhamos a superfície na área de autenticação e autorização em aplicativos ASP.NET Core. Sugiro consultar a documentação da Microsoft sobre os tópicos que discutimos neste artigo. Existem também muitos recursos integrados para autorização e gerenciamento de funções na plataforma .NET. Uma boa adição a este artigo poderia ser da Microsoft sobre autorização. a documentação