Pular para o conteúdo principal

OAuth2

Você já parou pra pensar como funciona esse sistema de login com contas do Google, GitHub, Facebook, LinkedIn, etc? Como esses aplicativos conseguem acesso aos seus dados sem precisar da sua senha?

Se você trabalha com TI, entender como funciona a segurança de APIs e autenticação é essencial. Vivemos num mundo onde tudo está interligado — e a base dessa integração segura se chama OAuth2 e OpenID Connect (OIDC).

Hoje em dia, quando você faz login com sua conta de outro serviço, na maioria das vezes está usando OAuth2 sem nem perceber. Ele é o protocolo que permite que apps acessem seus dados sem nunca ver sua senha. O OIDC entra como uma camada em cima do OAuth2 para permitir login de verdade, com identidade do usuário.

Esta documentação foi feita para te ajudar a entender, de forma simples, como tudo isso funciona por trás dos panos. Não vamos mergulhar em todos os detalhes técnicos, mas você sairá daqui com uma base sólida pra aprofundar seus estudos quando quiser.

Conceitos Básicos

Vamos simplificar com uma historinha:

Um usuário (user), usando um navegador (client), quer acessar um aplicativo — o chamado Service Provider (SP). Esse app precisa saber quem é o usuário. Mas, em vez de guardar as credenciais localmente, ele pede ajuda a um Identity Provider (IdP), como o Google ou Azure AD.

O IdP autentica o usuário e manda um sinal para o app dizendo: "esse cara é quem diz ser". Pode incluir informações extras: nome, email, foto, grupos... Tudo isso sem compartilhar a senha.

Aliás, a maioria das empresas hoje usa algum tipo de Identity Provider baseado em LDAP — geralmente o Active Directory da Microsoft. Você provavelmente já ouviu esse nome, mesmo sem saber exatamente o que era. O IdP concentra todas as informações do usuário: nome, email, telefone, foto, biometria (digital, íris), grupos de acesso e, claro, a senha. O papel dele é garantir: "essa pessoa é quem diz ser" — e avisar ao Service Provider: "pode seguir, é ele mesmo".

A grande sacada de ter um IdP é centralizar tudo num sistema único, acessível por várias aplicações ao mesmo tempo. Se você atualiza algo no IdP, essa mudança reflete em todo o ecossistema.

Autenticação ≠ Autorização

  • O IdP autentica: verifica quem você é.
  • A aplicação autoriza: decide o que você pode fazer.

Como o IdP prova quem você é?

Com base em fatores de autenticação. Pode ser um só ou uma combinação:

  • Algo que só você sabe: senha, perguntas de segurança.
  • Algo que só você possui: token, certificado, celular.
  • Algo que é único em você: digital, íris.

O que você pode fazer (autorização) é outra história. Isso é uma responsabilidade da aplicação ou do serviço saber se você pode ou não fazer algo, mas para isso também pode usar informações que estão no IdP, principalmente de grupos. Se o usuário esta é membro do grupo de admins então exibe outros tipos de informação. Observe que o IdP não autorizou nada, só informou o grupo, mas quem permitiu foi a aplicação.

LDAP Básico

Estudar LDAP ainda faz sentido, mas com foco estratégico. Não está morto, mas também não é mais o protagonista.

Quando vale a pena se aprofundar?

  • Infra Corporativa Legada
  • Ambientes Linux corporativos: Autenticação centralizada com OpenLDAP, SSSD, FreeIPA e é ideal para bom pra DevSecOps/Platform Eng. que mexe com servidores e IAM. Vamos quebrar isso em partes:
  • Integrações de autenticação com serviços que exigem LDAP. As vezees é mais fácil ligar direto via LDAP do que fazer SSO via SAML
  • Quando o Azure AD Domain Services entra no jogo, especialmente em cenários híbridos on-prem mais + Azure + AWS.

Não precisa virar especialista hardcore, mas entender a história e as bases técnicas é essencial.

Antes do IdP moderno:

  • Cada app guardava seu próprio banco de usuários
  • Senhas e dados sensíveis no mesmo banco da aplicação
  • Devs tinham acesso a dados pessoais
  • Nenhuma padronização entre apps
  • Remover ou alterar permissões era um caos manual
  • Auditoria e compliance? Quase impossível

Foi aí que entrou o LDAP, e depois o Active Directory, pra organizar a casa. Ele virou o "banco central" de identidades.

LDAP tradicional não foi feito pra Internet. Mesmo com LDAPS (versão segura), abrir ele pro mundo é perigoso mesmo que usemos VPN, Direct Connect, peearing de VPC, etc.

  • LDAP simples transmite senhas em texto claro (sem LDAPS)
  • Kerberos não lida bem com NAT/firewalls — depende de tempo sincronizado e tickets em tempo real
  • Alta latência pode quebrar autenticação — causa timeouts, falhas de login
  • Superfície de ataque aumenta — expor o AD é abrir uma porta pra ataques, mesmo com firewalls e ACLs

Tecnicamente o AD e a aplicação podem residir em datacenters diferente, mas em termos de segurança não é um bom cenário. Expor o AD está fora de cogitação e ele só deveria estar acessívia na intranet.

Por causa desse cenário foi necessário mudar a arquitetura e passar o SAML. Essa leitura vale a pena para entender basicamente como ele resolveu esse cenário e quais as limitações ainda existem.

Protocolo OAuth2

Ele permite que aplicações de terceiros acessem recursos em nome de um usuário, sem que o usuário precise compartilhar sua senha com essas aplicações. Seria como uma procuração registrada em cartório para que o aplicativo terceiro acesse em nome do usuário algum recurso deste usuário em algum lugar.

Conceitos Chaves

  • Resource Owner (Usuário): Quem possui os dados (ex: você).
  • Client (Aplicação): Quem quer acessar os dados (ex: um app de calendário querendo acessar seus eventos do Google Calendar). Pode ser qualquer tipo de aplicação, frontend, backend, mobile, etc.
  • Authorization Server: Quem autentica o usuário e emite tokens (ex: accounts.google.com).
  • Resource Server: API que protege os dados (ex: api.google.com/calendar).

Para apresentar os conceitos apresentamos o seguinte cenário:

  1. Uma aplicação quer acessar os dados do calendário do Google do usuário.
  2. Os dados, que são os recursos, ficam armazenados no banco de dados.
  3. Para proteger o acesso a esses dados temos uma API. No caso do Google é a api.google.com/calendar.
  4. A aplicação precisa ter acesso somente aos dados do usuário, porém com permissão limitada somente ao que ela precisa fazer.

alt text

Fazendo uma analogia, essa aplicação precisa ir no cartório e conseguir uma procuração para que fale em nome do usuário. O usuário precisa dar essa permissão avisando que permite que tais recursos estejam disponíveis para essa aplicação durante um tempo específico.

Observe que o Authorization Server protege a API. No caso do Google temos o Authorization Server do Google, mas se quisermos acessar o calendário da Microsoft, seria necessário acesso a uma outra API com um Authorization Server diferente, dedicado aos recursos da Microsoft. Um outro token seria gerado para acesso à API da Microsoft.

A permissão é dada através de escopos (Scopes), que são as políticas.

Essa procuração nada mais é do que um token, ou seja, uma credencial, na conta do usuário porém com limites de acesso. Na maioria dos casos esse token tem tempo de vida. Esse token possuirá um scope específico, por exemplo, scope=calendar.read calendar.write.

Com certeza você já teve uma experiência de um pop-up aparecendo para que você faça o login na sua conta para dar algum tipo de acesso. Ao conceder esse tipo de acesso, na verdade o que está acontecendo é a criação de um token específico só para essa conta.

Comparando esse cenário com o que se passa no SAML já podemos observar que uma aplicação conversa diretamente com outra sem a necessidade de fazer um redirect via navegador.

RecursoOAuth2SAML
PropósitoAutorização (com OIDC: login)Autenticação e Autorização
Tipo de tokenJSON (normalmente JWT)XML (SAML Assertions)
TransporteHTTP (geralmente via REST)XML via POST/Redirect
FormatoLeve, moderno, fácil de lerVerboso, XML
Usado emAPIs, SPAs, mobile, apps webEnterprise (SSO corporativo)
ComplexidadeSimples/modernoMais complexo
Suporte MobileSimFraco
Compatibilidade com JSONSimNão
Padrão extensível?SimLimitado

Outro conceito a se mensionar é o tempo de vida de um token (Lifetime e Expiração). Existem dois tipos de token:

  • Access token tem vida curta (~1h).
  • Refresh token pode durar dias/semanas (mas pode ser revogado).

Authorization Server

Na verdade, antes de fazer a requisição para a API desejada, é necessário conseguir o token de acesso com as devidas permissões. Vamos criar um fluxo inicial para explicar a teoria, as ainda não é o fluxo completo, somente um esboço inicial.

alt text

Ao conseguir acesso na primeira vez a aplicação deve guardar esse token por um período de tempo no seu banco de dados para que na segunda vez não precise refazer a solicitação para o usuário se o token ainda estiver com tempo de vida válido.

Falando sobre a segurança dessas transações.

Como um Authorization Server sabe que a requisição de acesso esta vindo de um client válido? Como ter certeza alguém não esta tentando criar uma request e enviando-a no lugar da aplicação real?

Temos préviamente que fazer o registro do client no Authorization Server que queremos acessar. Não existe uma forma segura de usar OAuth2 sem registro prévio do client no Authorization Server. Esse registro é o que autoriza o client a participar do fluxo. Sem isso, o Authorization Server não tem como validar nada.

Isso pode ser feito de duas forma:

  • Manual (mais comum):
    • Admins registram os clients via painel web do Authorization Server (ex: Keycloak, Auth0, Okta, Google, Facebook, GitHub, LinkedIn, Apple...)
    • Ideal pra apps que você controla ou quando o ambiente é mais fechado.
  • Automático (dinâmico):
    • Via Dynamic Client Registration (RFC 7591)

    • O client se auto-registra via API.

    • Exemplo: o client envia seus dados para um endpoint do Authorization Server, tipo:

      POST /register
      Content-Type: application/json

      {
      "client_name": "meu_app",
      "redirect_uris": ["https://meuapp.com/callback"],
      "grant_types": ["authorization_code"],
      "token_endpoint_auth_method": "none"
      }

      E teríamos uma resposta parecida com isso.

      {
      "client_id": "gerado_automaticamente",
      "client_secret": "se_for_confidencial"
      }

Alguns IdP suportam isso, mas geralmente precisa habilitar/configurar com cuidado (controle de quem pode se registrar). Isso é mais usado em ambientes dinâmicos, tipo federated login ou SaaS que aceita apps de terceiros.

O próprio Google e Facebook não suportam isso. Apesar alguns Authorization Server serem capaz de fazer isso, sempre é bom manter essa etapa manual enquanto possível para evitar possíveis ataques.

O Authorization Server (AS) grava no seu banco de dados uma ficha com os dados do client.

CampoO que é
client_idIdentificador único do client (gerado ou definido)
client_secret (opcional)Senha usada pra autenticar (só em apps confidenciais)
redirect_urisURLs permitidas para redirecionamento (evita phishing)
grant_typesFluxos autorizados (ex: authorization_code, client_credentials)
scopes permitidosQuais permissões o client pode pedir (ex: email, profile)
token_endpoint_auth_methodComo o client se autentica no token endpoint (ex: client_secret_post, none, etc)
client_nameNome amigável (mostrado ao usuário na tela de consentimento)
logo_uri (opcional)Logo do app, pra tela de consentimento
contacts (opcional)E-mails dos responsáveis
jwks_uri ou jwks(Para apps que assinam tokens ou usam mTLS)

O AS gera um client_id único e guarda esses dados em algum lugar, geralmente algum software como Vault, Cyberark ou até mesmo um banco de dados relacional. Se for confidencial (backend, etc), gera também um client_secret. Esses dois valores são como usuário/senha do client. Uma das informações mais importantes são as URLs de redirecionamento.

Quando o client fizer qualquer requisição, o AS consulta essa ficha, verifica se o fluxo (grant_type), redirect URI, scopes, etc estão permitidos e se algo estiver fora da ficha rejeita a requisição. Existem vários tipos de fluxo, mas falaremos sobre isso adiante em Grant Types.

O processo exato de registro depende do Authorization Server, não é o mesmo para todos.

Opaque Token

Ao fazer a solicitação, o Authorization Server saberá das permissões de acordo com o token passado. Esse é o famoso Bearer Token que é token que parece uma string aleatória, tipo:

d7f1b5b6-8a9c-4e33-98de-44c6f7ad0f4e

A parada é que ele não carrega info legível (diferente de um JWT) por isso chamado de opaque. Ele é só uma referência a um token válido, que só o Authorization Server consegue interpretar. Bearer tokens são usados como Access Token, principalmente quando:

  • Segurança é prioridade (menos risco de vazamento de info)
  • Você quer centralizar a validação no Authorization Server
  • O Resource Server precisa sempre chamar o Auth Server pra validar o token
  1. O cliente pede um token para o Authorization Server

  2. O Authorization Server retorna o opaque token caso o usuário permita.

  3. O cliente usa o token pra acessar uma API

  4. A API manda esse token de volta pro Authorization Server (ou introspecta ele) pra ver se:

    • É válido
    • Não expirou
    • Está associado ao usuário X
    • Tem os scopes corretos
  5. O authorization server retorna a resposta para a API sendo o mais importante o scope desse token.

  6. A API com o scope e as informações certas pode autorizar ou rejeitar a requisição.

É importante mensionar o token possui um tempo de validade que pode ser configurado dependendo o Authorization Server. No caso do Google é de 60 minutos. Geralmente IdP servers possuem esse tipo de configuração, como o Keycloak por exemplo.

O gargalo do Opaque Token é que Toda requisição à API exige uma ida até o Authorization Server pra validar o token.

Imagina 100k requests por minuto pra sua API. Se cada uma faz introspection:

  • Você satura o Auth Server
  • A API fica mais lenta (latência aumenta)
  • Possíveis timeouts, erros 5xx e quedas
  • Pode virar um SPOF (Single Point of Failure)

JWT Token

Se o token carregasse toda informação necessária, não seria necessário essa chamada ao Authorization Server para buscar os scopes e validar o token.

JWT (JSON Web Token) é um token auto-contido, que carrega informações dentro dele mesmo, de forma segura e compacta, em formato JSON, codificado com base64.

Um JWT é dividido em 3 partes, separadas por .. Veremos uma string enorme, mas se reparar bem existem alguns pontos no meio. O header, payload e signature contém muito mais campos do que vamos demonstrar, mas vamos primeiro andar depois correr.

A string abaixo foi dividida para demonstrar os pontos.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.      --> Header  
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvZSIsImlhdCI6MTYwOTAwMDAwMH0. --> Payload
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c --> Signature
  1. Header - Diz qual algoritmo foi usado na assinatura (ex: HS256, RS256)

    echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 --decode
    {"alg":"HS256","typ":"JWT"}%
  2. Payload - Carrega os dados (claims)

    echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvZSIsImlhdCI6MTYwOTAwMDAwMH0" | base64 --decode
    {"sub":"1234567890","name":"Joe","iat":160900000}
  3. Signature - Serve pra verificar se o token não foi alterado. Assinatura = HMAC_SHA256(base64url(header) + "." + base64url(payload), chave_secreta).

Vantagens:

  • Validação local (sem chamar o Auth Server)
  • Performance alta em grandes sistemas
  • Fácil de passar entre sistemas
  • Suporta assinaturas com chave pública/privada

Vamos analisar um JWT Token mais elaborado, porém fake ainda.

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMzQ1NiJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiJ5b3VyLWNsaWVudC1pZC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjExMjIzMzQ0NTU1NjY3NyIsImVtYWlsIjoiam9hb0BleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJzY29wZSI6InJlYWRfdXNlciB3cml0ZV9jb250ZW50IiwiaWF0IjoxNzE2ODAwMDAwLCJleHAiOjE3MTY4MDM2MDB9.Lj9sOH4ZyNzKxjEf1mGZdYeEQZleJqmb5Mg5k1nhFA8i9ZK6u2nCwZWzdk0rY6bCehgNfCvE9SLs9FSG9A

Conferindo a parte central (payload) temos isso.

{
"iss": "https://accounts.google.com",
"aud": "your-client-id.apps.googleusercontent.com",
"sub": "112233445556677",
"email": "[email protected]",
"email_verified": true,
"scope": "read_user write_content",
"iat": 1716800000,
"exp": 1716803600
}
ClaimDescrição
issQuem emitiu o token (issuer)
audQuem deve aceitar esse token (client_id)
subID único do usuário (Subject)
emailE-mail do usuário
scopePermissões dadas pelo usuário
iatEmitido em (issued at - timestamp)
expExpira em (expiration time - timestamp)

Quanto menor o tempo de vida do access token, menor o impacto se ele vazar. E como o access token dá acesso direto aos recursos protegidos, ele é altamente sensível.

Authorization Code Flow

Então como seria o fluxo completo?

alt text

Se reparou bem existem duas etapas diferentes.

  1. Pegar o Authorization Code: Esse passo serve pra autenticar o usuário e pedir consentimento. Não é um passo que já entrega acesso. Esse código é temporário e inútil sozinho.

  2. Trocar o Authorization Code por um Access Token. Esse é o passo onde a aplicação ganha permissão de verdade.

Por que são separados? Por segurança. Isso protege o Access Token real de exposição.

  • O primeiro passo passa pelo browser, onde tudo pode vazar.
  • O segundo passo é server-to-server, com client_secret ou PKCE.

Foi adicionado um step opcional para o OpenID Connect só para que entenda quando ele irá atuar. Não é obrigatório utilizar, mas é bom para entender o momento em que é usado para estudos futuros.

No ultimo passo, API do Google Calendar irá extrair o access token e fazer a verificação no Authorization Server. Esse passo só acontece caso for utilizado o token do tipo Opaque, pois se for JWT já estará assinado não necessitando dessa verificação junto ao Authorization Server. Porém, caso seja JWT, a API precisa ter a chave pública do Authorization Server disponível para fazer a verificação local. Todo Authorization Server tem o seu endpoint público para que todos tenham acesso a essa chave e cabe ao desenvolvedor guardá-la em algum lugar.

Existe um endpoint também no Authorization Server só para invalidar um token.

Todo esse processo é padronizado no RFC para que várias bibliotecas de diferentes linguagens possam seguir o mesmo fluxo.

Grant Types

Existem vários tipos de aplicações em que o client pode estar implementado. Poderia ser no frontend, backend, mobile, uma extensão no browser, em um software desktop, etc.

Quando um client esta no backend da aplicação (no servidor) chamamos de confidencial client, pois rodam em um ambiente seguro onde Auth Code tem menor chance de ser exposto. O public clients rodam em ambientes inseguros como um navegador, aplicativos Android e IOS, extensão de navegador, etc, onde o risco é muito maior.

O ideal é que o token trocado usando o auth code seja guardado o máximo possível no backend da aplicação, mas existem casos que não é possível. Mostramos um fluxo em que o client está no servidor (backend), mas existem cenários que isso não é possível. O Auth Code é válido por pouco tempo para ser trocado pelo Access Token e precisa do login do usuário. O Access token por outro lado já é o token que permite acesso ao recurso.

Existe uma extensão para esse tipo de concessão de auth code chamada PKCE (Proof Key for Code Exchange) em que um parâmetro verificador de código é gerado dinamicamente pelo client. Um desafio de código é criado e enviado ao chamar endpoint do Authorization Server. Seria como uma contra senha dinamicamente gerada pelo client que ficará armazenada no Authorization server e quando o Access token for trocado essa senha será utilizada para confirmação. É uma espécie de 2FA automático! Isso serve para que o Authorization Server saiba que é o mesmo client quem fez a requisição.

O grant_type é um parâmetro obrigatório da requisição feita para o token endpoint, ou seja, quando o client está pedindo um token de acesso ao Authorization Server. A seguir alguns exemplo de grant type principais.

  1. Authorization Code (com PKCE) → Frontend ou apps públicas

    • Usuário loga e dá consentimento
    • App pega um authorization code
    • Backend troca esse code por um access token
    • Considerado muito seguro
    • É o mesmo fluxo, porém com um parâmetro a mais.
  2. Client Credentials → Machine-to-machine

    • Sem usuário envolvido (tudo no sistema)
    • Só o cliente (app) se autentica com client_id + client_secret
    • Recebe um token para agir em nome próprio
    • Usado em microserviços, jobs, APIs internas
  3. Resource Owner Password Credentials (Depreciado)

    • App coleta username e password direto do usuário. Essa não é a tela de login do Authorization Server.
    • Manda pro Auth Server e recebe o token
    • Desencorajado hoje em dia, por riscos de segurança, pois de alguma forma coletamos a senha do usuário.
    • Só usado em sistemas muito controlados (tipo CLI interna) e first client aplication, mas ainda sim é desencorajado
  4. Refresh Token

    • Não é para login, mas para renovar o access token
    • Depois que o token expira, usa-se o refresh token para pegar um novo
    • Não precisa fazer login de novo
    • Usado para manter a sessão do usuário viva
    • Requer um fluxo anterior (authorization code ou password)
    • Quando obtemos o access token também podemos obter o refresh token (que possui um tempo muito maior de expiração, às vezes nunca expira). Porém o refresh token só funciona para gerar um novo token e não dá acesso aos recursos.
grant_typeNome / FlowRequer login do usuário?Usado quando?
authorization_codeAuthorization CodeSimTrocar código por token após login
authorization_code + PKCEAuthorization Code (com PKCE)SimVersão segura para SPAs e mobile
client_credentialsClient CredentialsNãoAuth máquina → máquina
password (obsoleto)Resource Owner Password CredentialsSimApps legados (não recomendado)
refresh_tokenRefresh TokenNãoRenovar access_token sem reautenticar
urn:ietf:params:oauth:grant-type:device_codeDevice Authorization FlowSim (fora do device)TVs, consoles, IoT
urn:ietf:params:oauth:grant-type:jwt-bearerJWT Bearer (RFC 7523)Não (token já assinado)Login federado entre serviços com JWT
urn:ietf:params:oauth:grant-type:token-exchangeToken Exchange (RFC 8693)DependeTrocar um token por outro (ex: delegação)

O valor de grant_type define como o Authorization Server deve processar a requisição de token, de acordo com o tipo de fluxo utilizado.

Você, como DevSecOps ou aspirante a Platform Engineer, deve saber:

O que saberPor quê?
Como os grant types funcionamPra entender integrações seguras e fluxos entre apps e serviços
Como proteger client_secrets e tokensPorque vazamento = brecha de segurança
Configurar IdPs (Keycloak, Auth0, etc)Porque muitas vezes o DevOps gerencia o Authorization Server
Políticas de expiração e revogaçãoPra manter segurança e controle de sessão
Monitorar e logar autenticaçõesPra responder incidentes e auditar acessos

Desenvolvedor implementa os fluxos então precisa conhecer com profundidade.
DevOps / DevSecOps / Platforma Engineer garante que estão seguros, escaláveis e bem configurados.