ASP.NET Core - Autenticação e Autorização

IMPORTANTE → Já existe uma versão mais atual deste artigo, utilizando ASP.NET 5. Para acessá-la, clique aqui

Um termo recorrente nas APIs é a autenticação e autorização, os Roles, Claims, Policies, JWT, oAuth e tudo mais. Embora pareça um assunto complexo, não é, e vamos desmistificar tudo neste guia!

Autenticação

A autenticação diz quem você é. Ela é responsável por identificar o usuário ou quem estiver acessando a API.

É normal hoje em dia vermos autenticação com terceiros, como Facebook, Google e agora até a Apple.

Este mecanismo, interno ou externo, garante que quem está acessando sua API é realmente quem ele diz ser.

Autorização

A autorização por sua vez, diz o que o usuário pode fazer. Ao contrário da autenticação, dificilmente você poderá terceirizar sua autorização, visto que ela faz parte do seu negócio e muitas vezes é específica para uma aplicação.

Roles, Claims e Policies

A autorização se dá por meio de Roles, Claims e Policies, que restringem ou permitem o acesso do usuário a determinada parte da aplicação.

Um Role (Perfil) é um papel, uma função, um cargo, algo que define acesso a um ou um grupo de usuários em partes da aplicação.

Normalmente trabalhamos com Roles em cenários mais simples, onde não necessitamos de muita interação, onde por exemplo, apenas sabendo que o usuário possui o papel de Gerente, já podemos tomar alguma decisão.

Um Claim (Afirmação) é algo mais imperativo em que na maioria dos casos utilizamos pra realizar alguma interação antes de deixar o usuário prosseguir.

Por exemplo, se precisamos validar se o usuário pertence a uma empresa específica, se ele realizou determinada venda ou qualquer outra validação que dependa de banco de dados por exemplo, este é um cenário interessante para os Claims.

As Policies (Políticas) nada mais são que as aplicações de Roles e Claims para determinar contratos de acessos a determinadas áreas de uma aplicação.

Você pode ter vários Roles e Claims compondo uma política que determina acesso a uma parte do seu sistema.

oAuth

Open Authentication nada mais é do que um padrão que especifica como fazemos as autenticações. Com a chegada de APIs abertas há um tempo atrás, foram necessárias algumas especificações mínimas de segurança e o oAuth foi criado.

Hoje ele se consolidou no mercado sendo utilizado por gigantes como Facebook, Twitter, Instagram, Spotify, Netflix dentre outros.

Token

Um token é uma chave encriptada, contendo informações do usuário, que só pode ser desencriptada com outra chave (privada) que fica no servidor.

Pode parecer estranho dizer que temos informações do usuário (Não sensíveis) em uma chave, mas ela garante que não precisamos armazenar estes tokens, tornando possível o que chamamos de Stateless Applications, ou aplicações sem estado.

Isto é fundamental para o escalonamento de qualquer aplicação. Afinal, sempre que uma máquina nova da sua aplicação sobe, sua memória não é copiada.

JWT

É a sigla para Json Web Token e pronuncia-se JOT. O JWT nada mais é que o armazenamento das informações do token no formato JSON.

Antes de prosseguir...

O processo de autenticação inicia-se em uma aplicação (Normalmente frontend) fazendo uma requisição para sua API.

Nesta requisição serão enviados o usuário e senha. Neste momento, nem preciso dizer que suas aplicações PRECISAM trafegar sobre HTTPS, isto é uma exigência do Google para indexar seu site.

Com o usuário e senha, é feito o processo normal de verificação do login, e caso tudo ocorra bem, sua API utiliza uma chave PRIVADA, que só ela tem para gerar um TOKEN contendo as informações que você desejar, no formato JWT.

Neste momento, você tem uma chave enorme, com todas as informações que colocou previamente (Nunca armazene informações sensíveis nos tokens), encriptada com uma chave privada que só a API e no formato JSON.

Abaixo um exemplo de chave:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFuZHJlYmFsdGllcmkiLCJTdG9yZSI6InVzZXIiLCJuYmYiOjE1NjE2MDI2NjYsImV4cCI6MTU2MjIwNzQ2NiwiaWF0IjoxNTYxNjAyNjY2fQ.y2cvZ02PLJG-BFPUAWn1fChTvS4rPOv\_iEEsLPd7eBg  

Essa chave decodificada tem o seguinte conteúdo

{'alg':'HS256','typ':'JWT'}{'unique\_name':'andrebaltieri','Store':'user','nbf':1561602666,'exp':1562207466,'iat':1561602666}  

No caso, além do nome de usuário e perfil, também informo a data/hora de criação e expiração da mesma, mas isto é uma particularidade de código.

Conforme comentei anteriormente, nós não ficamos “logados” na API, então após o Login, este token é retornado e deve ser enviado novamente a cada requisição.

Configurando sua API

A primeira coisa que precisamos fazer em nossa API é definir uma chave, um segredo, algo que só a API terá acesso.

Esta chave será utilizada para encriptar o token e afins posteriormente. Não vou focar em COMO, mas sim em O QUE você precisa fazer, e neste cenário, eu criei uma classe estática, chamada Settings.cs com minha chave.

Poderia utilizar o AppSettings dentre várias outras coisas, mas o cenário mais simples é este, e como nossa chave não tende a mudar, isto resolve o problema.

Importante: Se você mudar a chave, todos os tokens ainda ativos perderão a validade, já que será impossível desencriptá-los com uma chave diferente da original.

namespace store
{
    public static class Settings
    {
        public static string Secret = "43e4dbf0-52ed-4203-895d-42b586496bd4";
    }
}

Com nossa chave definida, precisamos gerar nossa chave, e este processo pode ser feito de diferentes maneiras também, com RSA e afins, mas um dos mais simples e que funciona é este abaixo:

var key = Encoding.ASCII.GetBytes(Settings.Secret);

Adicionando Autenticação

O primeiro passo é adicionar autenticação a nossa aplicação, e para isto vamos utilizar o método AddAuthentication do ASP.NET Core, que traz uma implementação padrão ótima para o que precisamos.

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

Note que definimos o AuthenticationScheme como JWT, o qual desejamos utilizar aqui. Além disso, há um DefaultChallenge que definimos também, mas ele não nos interessa no momento.

Depois de definido nosso esquema, precisamos dizer como nossos tokens vão funcionar, quais configurações irão ter, e isto é feito no método AddJwtBearer, conforme abaixo.

.AddJwtBearer(x =>
{
    x.RequireHttpsMetadata = false;
    x.SaveToken = true;
    x.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        ValidateIssuer = false,
        ValidateAudience = false
    };
});

O que mais importa aqui são os TokenValidationParameters, que neste caso, informamos nossa chave, chamando a função SymmetricSecurityKey do .NET para gerar a chave segura.

Precisamos também, informar ao ASP.NET Core para utilizar a autenticação, e isto é feito no método Configure.

app.UseAuthentication();

Por fim, é importante sempre utilizar o SetCompatibilityVersion para garantir que nada quebrará em versões futuras. O resultado final da classe Startup.cs é este abaixo, mas calma que ele ainda irá mudar.

public class Startup
{    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors();
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

        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, IHostingEnvironment env)
    {
        app.UseCors(x => x
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());

        app.UseAuthentication();

        app.UseMvc();
    }
}

Adicionando Autorização

Se utilizamos o AddAuthentication para autenticação, o AddAuthorization vai para autorização. Intuitivo né.

Neste cenário teremos dois Claims, um para usuário e outro para administrador, e estes são configurados neste formato, também no método ConfigureServices da classe Startup.cs.

services.AddAuthorization(options =>
{
    options.AddPolicy("user", policy => policy.RequireClaim("Store", "user"));
    options.AddPolicy("admin", policy => policy.RequireClaim("Store", "admin"));
});

No código acima eu crio duas Policies e dois Claims, uma para cada cenário. Em adicional, sempre definimos uma “chave” para eles, no caso utilizei o nome da aplicação, “Store”.

Deste modo definimos as políticas e afirmações, mas ainda não estamos utilizando elas, veremos isto nos Controllers logo mais.

Políticas Globais

Particularmente, em diversos cenários acho mais fácil restringir TODA a API e liberar só o que preciso, conforme a demanda.

Desta forma, podemos definir políticas globais, dizendo que TODO endpoint da nossa API requer autenticação.

Isto é feito também no ConfigureServices, no método AddMvc, desta forma.

services.AddMvc(config =>
{
    var policy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();
    config.Filters.Add(new AuthorizeFilter(policy));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

O resultado final da nossa classe Startup.cs pode ser conferido abaixo.

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

namespace shop
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors();
            services.AddMvc(config =>
            {
                var policy = new AuthorizationPolicyBuilder()
                                .RequireAuthenticatedUser()
                                .Build();
                config.Filters.Add(new AuthorizeFilter(policy));
            }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            services.AddAuthorization(options =>
            {
                options.AddPolicy("user", policy => policy.RequireClaim("Store", "user"));
                options.AddPolicy("admin", policy => policy.RequireClaim("Store", "admin"));
            });

            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, IHostingEnvironment env)
        {
            app.UseCors(x => x
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());

            app.UseAuthentication();

            app.UseMvc();
        }
    }
}

Gerando o Token

Para este exemplo, criei um serviço para geração do Token, para não ficar tudo no Controller, mas sinta-se à vontade em fazer de forma diferente.

Na verdade, tudo que precisamos fazer aqui é buscar um usuário do banco de dados, gerar um token e aplicar as políticas ao usuário. Não vou cobrir como buscar o usuário do banco, OK, não é o foco deste artigo!

O ASP.NET Core possui um JwtSecurityTokenHandler, que vai fazer todo o trabalho duro para nós, precisamos apenas passar para ele um TokenDescriptor, que nada mais é do que a definição do token a ser gerado, com os Claims a data de expiração e as credenciais de acesso.

Neste cenário, para não criar muitas classes também, adicionei uma propriedade Token no User, apenas para poder retornar o User direto para tela, sem precisar converter ele para uma ViewModel. Não faça isto em casa!

var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(Settings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(new Claim[]
    {
        new Claim(ClaimTypes.Name, user.Username),
        new Claim("Store", user.Role)
    }),
    Expires = DateTime.UtcNow.AddDays(7),
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);

No TokenDescriptor, temos o Subject, que define o Claim principal, e seus Roles. Neste caso também deixei apenas um Role no usuário para facilitar. Se tiver mais de um Role, faz um ForEach e tá tudo certo.

Movendo adiante temos o Expires que é o tempo de vida do Token, como é uma API de exemplo e não quero ficar fazendo Refresh do Token, utilizei 7 dias, mas normalmente utilizo duas horas.

Por fim temos as credenciais de acesso, novamente utilizando nossa chave. O resultado final do nosso UserService é este.

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using shop.Database;
using store.Models;

namespace store.Services
{
    public class UserService
    {
        public User Authenticate(string username, string password)
        {
            var user = Context.Users.Where(x => x.Username == username && x.Password == password).FirstOrDefault();

            if (user == null)
                return null;

            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(Settings.Secret);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(ClaimTypes.Name, user.Username),
                    new Claim("Store", user.Role)
                }),
                Expires = DateTime.UtcNow.AddDays(7),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            user.Token = tokenHandler.WriteToken(token);

            user.Password = null;

            return user;
        }
    }
}

Permitindo Acesso Anônimo

Quando adicionamos as políticas globais, nós restringimos todo acesso à nossa API, porém pelo menos o Login precisa ser liberado, e para isto, utilizamos o decorador AllowAnonymous nas ações dos nossos controladores.

[HttpPost("login")]
[AllowAnonymous]
public IActionResult Authenticate([Fro.....

Então, sempre que quiser liberar acesso a uma determinada ação do seu controlador, basta utilizar este decorador.

O resultado final do nosso AccountController fica desta forma.

using System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using store.Models; using store.Services;

namespace store.Controllers
{
    [Route("v1/account")]
    public class AccountController : Controller
    {
        [HttpPost("login")]
        [AllowAnonymous]
        public IActionResult Authenticate([FromBody]User model)
        {
            try
            {
                var service = new UserService();
                var user = service.Authenticate(model.Username, model.Password);

                if (user == null)
                    return BadRequest(new { message = "Usuário ou senha inválidos" });

                return Ok(user);
            }
            catch (Exception ex)
            {
                return BadRequest(ex);
            }
        }
    }
}

Restringindo Acesso por Perfil

Toda nossa API está restrita, porém a restrição é apenas para usuários anônimos, ou seja, se o usuário estiver autenticado, ele já tem acesso.

Nós criamos duas políticas de acesso para a API, uma para usuário (user) e outra para administrador (admin), definidas lá no Startup.cs.

Para utilizar estas políticas e restringir o acesso por perfil, basta utilizar o decorador Authorize, informando o perfil que deve ter acesso. É importante lembrar que as Strings definidas são Case Sensitive, então cuidado com isto.

[HttpGet]
[Route("")]
[Authorize("User")]
public IList<Category> Get() => Database.Context.Categories();

[HttpGet]
[Route("")]
[Authorize("Admin")]
public IList<Category> Get() => Database.Context.Categories();

Pronto! Temos uma API com autenticação e autorização com Claims e Policies de uma forma simples e fácil!

Implementação

Populares

Priority Queue

Priority Queue ou fila prioritária é um tipo de lista inclusa no C# 10 que permite que seus itens...


Implicit Operators no C#

Implicit Operators permitem adicionar comportamentos de conversão a objetos Built In ou complexos...


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

Este artigo atualmente utiliza a versão 5.0.0-rc.1 do ASP.NET/.NET, o que significa que ainda não...


Clean Code - Guia e Exemplos

Saiba como manter seu código limpo (Clean Code) seguindo algumas práticas sugeridas pelo Robert C...


Git e GitHub - Instalação, Configuração e Primeiros Passos

Git é um sistema de controle de versões distribuídas, enquanto GitHub é uma plataforma que tem o ...


Compartilhe este artigo



Conheça o autor

Me dedico ao desenvolvimento de software desde 2003, sendo minha maior especialidade o Desenvolvimento Web. Durante esta jornada pude trabalhar presencialmente aqui no Brasil e Estados Unidos, atender remotamente times da ?ndia, Inglaterra e Holanda, receber 8x Microsoft MVP e realizar diversas consultorias em empresas e projetos de todos os tamanhos.





2.651

Aulas disponíveis

249

horas de conteúdo

64.551

Alunos matriculados

45.679

Certificados emitidos





Comece de graça agora mesmo!

Temos mais de 19 cursos totalmente de graça e todos com certificado de conclusão.

Começar


Prefere algo mais Premium?

Conheça nossos planos



Premium semestral

Compra única, parcelada em até
12x no cartão de crédito


12x R$

49

,78

=R$ 597,36
  • 6 meses de acesso
  • Acesso à todo conteúdo
  • Emissão de Certificado
  • Tira Dúvidas Online
  • 59 cursos disponíveis
  • 10 carreiras disponíveis
  • 161 temas de tecnologia
  • Conteúdo novo todo mês
  • Encontros Premium

Começar agora

Política de privacidade

Premium anual

Compra única, parcelada em até
12x no cartão de crédito


12x R$

84

,78

=R$ 1.017,36
  • 1 ano de acesso
  • Acesso à todo conteúdo
  • Emissão de Certificado
  • Tira Dúvidas Online
  • 59 cursos disponíveis
  • 10 carreiras disponíveis
  • 161 temas de tecnologia
  • Conteúdo novo todo mês
  • Encontros Premium

Começar agora

Política de privacidade



Precisa de ajuda?

Dúvidas frequentes



  • Posso começar de graça?

    Sim! Basta criar sua conta gratuita no balta.io e começar seus estudos. Nós contamos com diversos cursos TOTALMENTE gratuitos e com certificado de conclusão.

  • Vou ter que pagar algo?

    Nós temos cursos gratuitos e pagos, porém você não precisa informar nenhum dado de pagamento para começar seus estudos gratuitamente conosco. Os cursos gratuitos são completos e com certificado de conclusão, você não paga nada por eles.

    Porém, caso queira algo mais Premium , você terá acesso à diversos benefícios que vão te ajudar ainda mais em sua carreira.

  • Por onde devo começar?

    Siga SEMPRE as nossas Carreiras , elas vão te orientar em todos os sentidos. Os cursos já estão organizados em categorias e carreiras para facilitar seu aprendizado.
    Nossa sugestão para aprendizado é começar pelo Backend e seguindo para Frontend e Mobile.

    • Backend
    • Frontend
    • Mobile

  • Os cursos ensinam tudo que preciso?

    Nenhum curso no mundo vai te ensinar tudo, desculpa ser sincero! Os cursos são uma base, eles fornecem por volta de 30% do que você precisa aprender, o resto é com você, com dedicação e MUITA prática.

  • O que eu devo estudar?

    Java ou .NET? Angular ou React? Xamarin ou Flutter? A resposta é simples e direta: "Você já sabe o básico?"

    Se você ainda não sabe BEM o básico, ou seja, os fundamentos, OOP, SOLID, Clean Code, está perdendo tempo estudando Frameworks ou até coisas mais avançadas como Docker. Foque nos seus objetivos primeiro.
    Agora se você está indeciso sobre qual Framework estudar, a boa notícia é que o mercado neste momento está bem aquecido e você tem várias oportunidade. Desta forma o que levaríamos em conta para tomar esta decisão seria:

    • Já sei o básico
    • O Framework/Tecnologia tem mercado onde eu estou (região)
    • O Framework/Tecnologia é utilizado em uma empresa onde quero atual
    • O Framework/Tecnologia resolve meu problema
    • Eu gosto de utilizar o Framework/Tecnologia

  • Estou pronto para estudar no balta.io?

    Com certeza! O primeiro passo é começar e você pode fazer isto agora mesmo!

    Começar de graça

Ainda tem dúvidas?





Assine nosso Newsletter

Receba em primeira mão todas as nossas novidades.

Cadastrar


balta.io