O que é autenticação e autorização

Autenticação e autorização são os dois mecanismos que controlam o acesso a qualquer sistema: a autenticação descobre quem você é, e a autorização decide o que você pode fazer. Neste artigo, vamos entender cada conceito em detalhes e ver como esses modelos se aplicam aos cenários de APIs.

Embora pareçam sinônimos, os dois termos respondem perguntas diferentes. Por isso, confundir um com o outro costuma gerar falhas de segurança difíceis de identificar.

O que é autenticação e autorização

Em resumo, autenticação e autorização formam uma sequência. Primeiro o sistema confirma a identidade de quem faz a requisição e, só depois disso, avalia as permissões dessa identidade.

A documentação da Microsoft resume bem essa divisão no guia Authentication vs. authorization. Segundo o texto, a autenticação prova que você é quem diz ser, enquanto a autorização concede ou nega o acesso a um recurso.

Na prática, uma analogia ajuda bastante. Na portaria de um prédio comercial, o documento com foto confirma a sua identidade, e o crachá liberado define em quais andares você pode entrar.

O que é autenticação

Autenticação é o processo que diz quem você é. Por exemplo, em um processo de autenticação interno ou externo, eu garanto que sou o André Baltieri por meio do e-mail xyz@balta.io.

Esse processo acontece de diferentes maneiras. Você pode usar usuário e senha, e-mail e senha ou mesmo redes sociais, nos famosos botões "Login com Google" e "Login com Facebook".

De qualquer forma, não importa o meio, o objetivo é sempre o mesmo. O sistema precisa garantir que quem diz ser "xyz@balta.io" é realmente o "André Baltieri".

Além disso, a autenticação pode combinar mais de um fator. Algo que você sabe (senha), algo que você tem (celular) e algo que você é (biometria) compõem a chamada autenticação multifator.

Como garantir que eu sou eu

O primeiro passo é garantir que uma pessoa está ligada a um e-mail ou a um nome de usuário. Felizmente, esse processo é relativamente simples.

No caso do e-mail, basta enviar um código ao endereço informado durante o cadastro. Assim, o sistema confirma que a pessoa tem acesso real àquela caixa de entrada antes de ativar a conta.

Fluxo de verificação de e-mail com envio de código no cadastro, primeira etapa de autenticação e autorização de um usuário
O código enviado por e-mail confirma que o endereço informado pertence de fato a quem fez o cadastro.

Depois dessa confirmação, o login passa a comparar as credenciais informadas com as que o sistema guarda. Nesse ponto, a senha nunca deve ficar armazenada em texto puro, e sim como um hash seguro.

Autenticação externa

Outro tipo de autenticação muito comum usa alguma rede social ou serviço externo. Nesse modelo, você delega a responsabilidade para outro servidor, o que costuma ser uma boa escolha.

Afinal, o processo de verificação de contas do Google ou da Microsoft, por exemplo, é bem mais complexo e completo do que o seu provavelmente será. Com isso, você ganha recursos como autenticação multifator sem precisar construir nada.

Nesse formato, a aplicação gera uma solicitação no momento do login e redireciona o usuário para a plataforma externa. Em seguida, o usuário retorna para a nossa plataforma com um token, e então damos andamento na requisição.

Qualquer empresa pode fornecer esse serviço, desde que implemente o OIDC (OpenID Connect), um protocolo aberto de autenticação. A especificação OpenID Connect Core 1.0 define esse padrão, que funciona como uma camada de identidade sobre o OAuth 2.0.

Existem servidores OIDC prontos, como o Identity Server e o Keycloak. Ambos fornecem uma ótima implementação e são completos em recursos, e você pode conhecer o primeiro no artigo criando seu primeiro Identity Server.

Resumindo, se o balta.io tivesse uma implementação do OIDC, você poderia adicionar um botão "Login com balta" no seu site. Ou seja, o balta.io passaria a atuar como provedor de identidade para outras aplicações.

Como o custo e o risco de manter um OIDC próprio são altos, a recomendação é sempre começar pelo mais básico. Nesse caso, o ponto de partida ideal é uma autenticação baseada em OAuth com JWT.

O que é autorização

Se autenticação diz quem você é, autorização diz o que você pode fazer. Aqui entram os famosos Roles ou Perfis, que no ASP.NET se estendem para políticas (Policies) e afirmações (Claims).

Enquanto a autenticação segue em diversas vezes uma padronização, a autorização não tem necessariamente uma regra. Eu mesmo já criei sistemas onde, ao invés de Roles, utilizávamos Tags.

De qualquer forma, a ideia aqui é simples. Sabendo que o "xyz@balta.io" é o "André Baltieri", o que ele pode fazer dentro deste sistema?

Note que estamos falando DESTE sistema, pois a autorização varia muito. Inclusive, ela muda entre módulos, páginas e até mesmo botões.

Podem existir páginas que eu posso ver, mas não posso editar. Portanto, a autorização precisa tratar todos esses detalhes.

Na maioria dos casos, a autorização também é CUMULATIVA. Ou seja, eu posso ter vários perfis como "admin", "employee" e "sales", e cada um deles tem funções distintas que se acumulam.

Roles, claims e policies na autenticação e autorização

No ecossistema .NET, a dupla autenticação e autorização ganha três ferramentas principais. As Roles agrupam usuários por função, as Claims descrevem atributos do usuário e as Policies combinam requisitos em uma regra com nome.

Por exemplo, uma claim pode informar o e-mail, o departamento ou o número de matrícula do usuário. Em seguida, uma policy verifica se essa claim existe ou se tem um valor específico.

O trecho abaixo mostra como registrar duas políticas em uma API com Minimal APIs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication().AddJwtBearer();

// Políticas baseadas em role e em claim
builder.Services.AddAuthorizationBuilder()
    .AddPolicy("AdminOnly", policy => policy.RequireRole("admin"))
    .AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));

var app = builder.Build();

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

app.MapGet("/reports", () => "Sales report")
    .RequireAuthorization("EmployeeOnly");

app.MapDelete("/users/{id}", (int id) => Results.NoContent())
    .RequireAuthorization("AdminOnly");

app.Run();

Nesse exemplo, a rota /reports exige apenas a claim EmployeeNumber. Por outro lado, a rota de exclusão de usuários exige a role admin, o que reforça a ideia de permissões granulares.

Segundo a documentação de autorização baseada em roles, o ASP.NET Core também permite exigir várias roles ao mesmo tempo. Basta encadear chamadas de RequireRole na mesma política.

Por onde começar

Existem modelos prontos, como o Keycloak e o Identity Server, que implementam o OIDC. Além deles, frameworks como o ASP.NET Identity permitem criar um sistema de usuários em poucos minutos.

No entanto, minha recomendação é que você implemente manualmente um sistema de autenticação, pelo menos para fins de estudo. Dessa forma, você conhece as entranhas e sabe tudo o que está acontecendo.

Por isso, eu te convido a implementar um modelo de autenticação híbrido, com autenticação interna e externa. A proposta é usar OAuth com ASP.NET e Blazor para ver as duas abordagens funcionando juntas.

Se você quiser um ponto de partida prático, o artigo sobre JWT e Bearer no ASP.NET 7 mostra a implementação passo a passo. Também vale ler sobre como customizar perfis do ASP.NET Identity.

Autenticação e autorização em APIs

Como você já deve imaginar, tudo começa na API. Afinal, a segurança no lado do cliente é sempre fraca, então todo processo de autenticação e autorização deve rodar no servidor.

Armazenar um usuário e seus perfis é uma tarefa relativamente simples, assim como ler esses dados e enviá-los para a tela. O problema está em manter essas informações do outro lado da conexão.

Deixa eu te explicar melhor. Em APIs nós nunca ficamos autenticados ou autorizados de forma permanente, porque a API repete esse processo a cada requisição.

O problema da sessão em memória

Existe um motivo para isso. Até um tempo atrás, utilizávamos sessão para manter esses dados em memória e deixar o usuário conectado.

Com a distribuição das aplicações em diferentes servidores, manter o usuário conectado dessa forma deixou de ser viável. Isso acontece porque os servidores não compartilham memória entre si.

Então imagine que você acessou o site do balta.io agora e fez o login. O servidor guardou seus dados de login em memória, e você está assistindo a uma aula com 10 minutos de duração.

Após terminar a aula, você clica no botão concluir, porém o servidor onde você se autenticou está ocupado, com muita carga. Nesse momento entra em ação o Load Balancer, ou balanceador de carga.

Ele rapidamente identifica que existe outro servidor do balta.io livre e então envia sua requisição para lá. Como os servidores não compartilham memória, você não está autenticado nesse segundo servidor, e a requisição falha com o erro 401.

A própria documentação do ASP.NET Core sobre estado de sessão descreve esse cenário. Segundo o texto, o provedor em memória exige sessões fixas (sticky sessions), o que prejudica a escalabilidade.

Tokens de acesso na autenticação e autorização

Mudando o cenário para o modelo que usamos hoje, você se autentica a cada requisição, e esse erro deixa de acontecer. Nesse modelo, a autenticação e autorização dependem de um token que viaja junto com cada chamada.

Primeiro, o servidor gera um token de acesso assinado com uma chave privada que só ele conhece. Essa chave precisa ser comum entre todos os servidores da aplicação.

Em seguida, a cada requisição, o frontend envia esse token, normalmente no cabeçalho Authorization com o esquema Bearer. Veja um exemplo:

GET /api/courses HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Com o token em mãos, a API usa a chave para validar a assinatura e então lê os valores do usuário. Além do nome e do e-mail, o token pode carregar quaisquer outros valores, como roles e claims.

Vale um detalhe importante: um JWT comum é assinado, mas não é criptografado. Por isso, qualquer pessoa consegue ler o conteúdo, e você nunca deve colocar dados sensíveis dentro dele.

Se você quiser entender a estrutura desse token em detalhes, leia o artigo o que é JWT. Lá você vê o cabeçalho, o payload e a assinatura, que compõem cada token.

Você também pode armazenar os tokens para uma validação mais rígida, como uma lista de tokens revogados. Contudo, isso implica em pelo menos uma consulta ao banco de dados a cada requisição autenticada que a API recebe.

Autenticação e autorização nos códigos 401 e 403

O protocolo HTTP também reflete a separação entre autenticação e autorização. Para isso, ele usa dois códigos de status distintos.

O código 401 Unauthorized indica que a requisição não trouxe credenciais válidas. Por exemplo, o token pode estar ausente, expirado ou com a assinatura inválida.

Já o código 403 Forbidden indica que o usuário está autenticado, mas não tem permissão para acessar o recurso. Em outras palavras, a API sabe quem você é, mas a resposta continua sendo não.

A documentação sobre JWT bearer no ASP.NET Core reforça essa distinção. Segundo o guia, a API não deve redirecionar o usuário para o provedor de identidade, pois obter o token é responsabilidade do cliente.

Boas práticas de autenticação e autorização

Algumas práticas valem para qualquer tecnologia. A primeira delas é nunca confiar em validações feitas apenas no frontend, porque o usuário controla o navegador.

Além disso, defina tokens com tempo de expiração curto e use refresh tokens para renovar o acesso. Assim, um token vazado perde a validade rapidamente.

Também proteja a chave de assinatura com o mesmo cuidado de uma senha de banco de dados. Guarde a chave em um cofre de segredos e nunca a coloque no repositório de código.

Por fim, aplique o princípio do menor privilégio ao modelar a autorização. Cada perfil recebe apenas as permissões necessárias para executar o seu trabalho, e nada além disso.

Para aprofundar esse tema, o artigo sobre boas práticas de segurança em APIs ASP.NET Core traz outras recomendações. Ele complementa bem os conceitos de autenticação e autorização que vimos aqui.

Conclusão

Neste artigo, vimos como dois modelos de acesso, mesmo com anos de vida, ainda nos fornecem visões diferentes. A autenticação responde quem é o usuário, enquanto a autorização responde o que ele pode fazer.

Entendemos também como autenticação e autorização funcionam dentro das APIs. Com tokens enviados a cada requisição, a aplicação escala horizontalmente sem depender de memória compartilhada entre servidores.

Portanto, o próximo passo é colocar a mão no código. Comece com JWT e políticas simples e, quando o sistema crescer, avalie um provedor OIDC como o Keycloak ou o Identity Server.

FAQ

Qual a diferença entre autenticação e autorização?

A autenticação confirma a identidade do usuário, e a autorização define quais recursos esse usuário pode acessar. A autenticação sempre acontece primeiro.

O que significa o erro 401 em uma API?

O erro 401 indica que a requisição não trouxe credenciais válidas. Normalmente, o token está ausente, expirado ou com assinatura inválida.

O que significa o erro 403 em uma API?

O erro 403 indica que o usuário está autenticado, mas não tem permissão para acessar o recurso solicitado.

OAuth serve para autenticação?

O OAuth 2.0 é um protocolo de autorização. Para autenticar usuários, o mercado usa o OpenID Connect, que funciona como uma camada de identidade sobre o OAuth.

Posso guardar dados sensíveis dentro de um JWT?

Não. Um JWT comum é apenas assinado, então qualquer pessoa consegue decodificar e ler o conteúdo do payload.

Preciso de um servidor OIDC próprio?

Na maioria dos casos, não. Começar com JWT e, se necessário, adotar uma solução pronta como Keycloak ou Identity Server custa bem menos.