Português (Portugal)
  • autenticação
  • autorização
  • oauth
  • openid-connect
  • oidc
  • aplicação
  • api

Proteger aplicações em nuvem com OAuth 2.0 e OpenID Connect

Um guia completo para proteger as tuas aplicações em nuvem com OAuth 2.0 e OpenID Connect e como oferecer uma ótima experiência ao utilizador com autenticação e autorização.

Gao
Gao
Founder

Introdução

As aplicações em nuvem são a tendência hoje em dia. Embora o tipo de aplicação varie (web, móvel, desktop, etc.), todas têm um backend em nuvem que fornece serviços como armazenamento, computação e bases de dados. Na maioria das vezes, estas aplicações precisam de autenticar utilizadores e autorizá-los a aceder a determinados recursos.

Embora seja possível criar mecanismos de autenticação e autorização próprios, a segurança tornou-se uma das principais preocupações ao desenvolver aplicações em nuvem. Felizmente, a nossa indústria tem padrões testados em batalha como OAuth 2.0 e OpenID Connect para nos ajudar a implementar autenticação e autorização seguras.

Este post tem as seguintes suposições:

  1. Tens um entendimento básico de desenvolvimento de aplicações (web, móvel, ou qualquer outro tipo).
  2. Já ouviste falar dos conceitos de autenticação e autorização.
  3. Já ouviste falar de OAuth 2.0 e OpenID Connect.

Sim, "ouvir falar" é suficiente para o 2 e o 3. Este post usará exemplos do mundo real para explicar conceitos e ilustrar o processo com diagramas. Vamos começar!

OAuth 2.0 vs. OpenID Connect

Se estiveres familiarizado com OAuth 2.0 e OpenID Connect, podes continuar a ler porque cobriremos alguns exemplos do mundo real nesta seção; se fores novo nestes padrões, também é seguro continuar, pois iremos introduzi-los de uma forma simples.

OAuth 2.0

OAuth 2.0 é uma estrutura de autorização que permite a uma aplicação obter acesso limitado a recursos protegidos em outra aplicação em nome de um utilizador ou da própria aplicação. A maioria dos serviços populares como Google, Facebook e GitHub usa OAuth 2.0 para login social (por exemplo, "Entrar com Google").

Por exemplo, tens uma aplicação web MyApp que quer aceder ao Google Drive do utilizador. Em vez de pedir ao utilizador para partilhar as suas credenciais do Google Drive, a MyApp pode usar OAuth 2.0 para solicitar acesso ao Google Drive em nome do utilizador. Aqui está um fluxo simplificado:

Neste fluxo, a MyApp nunca vê as credenciais do Google Drive do utilizador. Em vez disso, recebe um token de acesso do Google que lhe permite acessar o Google Drive em nome do utilizador.

Nos termos do OAuth 2.0, a MyApp é o client, o Google é tanto o servidor de autorização quanto o servidor de recursos para simplificar. No mundo real, muitas vezes temos servidores de autorização e de recursos separados para oferecer uma experiência de single sign-on (SSO). Por exemplo, o Google é o servidor de autorização e pode ter vários servidores de recursos como Google Drive, Gmail e YouTube.

Note que o fluxo de autorização real é mais complexo do que isso. O OAuth 2.0 tem diferentes tipos de concessão, âmbitos e outros conceitos que deves estar ciente. Vamos deixar isso de lado por agora e passar para o OpenID Connect.

OpenID Connect (OIDC)

OAuth 2.0 é ótimo para autorização, mas podes notar que não tem uma maneira de identificar o utilizador (ou seja, autenticação). OpenID Connect é uma camada de identidade em cima do OAuth 2.0 que adiciona capacidades de autenticação.

No exemplo acima, a MyApp precisa saber quem é o utilizador antes de iniciar o fluxo de autorização. Note que há dois utilizadores envolvidos aqui: o utilizador da MyApp e o utilizador do Google Drive. Neste caso, a MyApp precisa conhecer o utilizador da sua própria aplicação.

Vamos ver um exemplo simples, assumindo que os utilizadores podem entrar na MyApp usando nome de utilizador e senha:

Como estamos a autenticar o utilizador da nossa própria aplicação, normalmente não há necessidade de pedir permissão como o Google fez no fluxo OAuth 2.0. Entretanto, precisamos de algo que possa identificar o utilizador. O OpenID Connect introduz conceitos como ID token e ponto de extremidade de userinfo para nos ajudar com isso.

Podes notar que o provedor de identidade (IdP) é um novo participante autónomo no fluxo. É o mesmo que o servidor de autorização no OAuth 2.0, mas para maior clareza, usamos o termo IdP para mostrar que é responsável pela autenticação do utilizador e gestão de identidade.

Quando o teu negócio cresce, podes ter várias aplicações que compartilham a mesma base de dados de utilizadores. Assim como o OAuth 2.0, o OpenID Connect permite ter um único servidor de autorização que pode autenticar utilizadores para várias aplicações. Se o utilizador já entrou em uma aplicação, não precisa de inserir as suas credenciais novamente quando outra aplicação o redireciona para o IdP. O fluxo pode ser feito automaticamente sem interação do utilizador. Isso é chamado de single sign-on (SSO).

De novo, este é um fluxo altamente simplificado e há mais detalhes sob o capô. Por enquanto, vamos avançar para a próxima seção para evitar sobrecarga de informação.

Tipos de aplicação

Na seção anterior, usamos aplicações web como exemplos enquanto o mundo é mais diversificado do que isso. Para um provedor de identidade, a linguagem de programação exata, o framework ou a plataforma que usas realmente não importa. Na prática, uma diferença notável é se a aplicação é um cliente público ou um cliente privado (confiável):

  • Cliente público: Um cliente que não pode manter as suas credenciais confidenciais, o que significa que o proprietário do recurso (utilizador) pode acessá-las. Por exemplo, uma aplicação web a correr em um navegador (por exemplo, aplicação de página única).
  • Cliente privado: Um cliente que tem a capacidade de manter as suas credenciais confidenciais sem as expor aos utilizadores (proprietários dos recursos). Por exemplo, uma aplicação web a correr num servidor (por exemplo, aplicação web do lado do servidor) ou um serviço de API.

Com isso em mente, vamos ver como o OAuth 2.0 e o OpenID Connect podem ser usados em diferentes tipos de aplicação.

"Aplicação" e "cliente" podem ser usados de forma intercambiável no contexto deste post.

Aplicações web a correr num servidor

A aplicação corre num servidor e serve páginas HTML para os utilizadores. Muitos frameworks web populares como Express.js, Django, e Ruby on Rails caem nesta categoria; e frameworks backend-for-frontend (BFF) como Next.js e Nuxt.js também estão incluídos. Estas aplicações têm as seguintes características:

  1. Como um servidor permite apenas acesso privado (não há maneira de utilizadores públicos verem o código ou credenciais do servidor), é considerado um cliente privado.
  2. O fluxo geral de autenticação de utilizador é o mesmo que discutimos na seção "OpenID Connect".
  3. A aplicação pode usar o token de ID emitido pelo provedor de identidade (ou seja, o provedor de OpenID Connect) para identificar o utilizador e exibir conteúdo específico do utilizador.
  4. Para manter a aplicação segura, a aplicação geralmente usa o fluxo de código de autorização para a autenticação do utilizador e para obter tokens.

Entretanto, a aplicação pode precisar de aceder a outros serviços de API internos numa arquitetura de microsserviços; ou é uma aplicação monolítica que precisa de controle de acesso para diferentes partes da aplicação. Discutiremos isto na seção "Protege a tua API".

Aplicações de página única (SPAs)

A aplicação corre no navegador de um utilizador e comunica-se com o servidor via APIs. React, Angular e Vue.js são frameworks populares para construir SPAs. Estas aplicações têm as seguintes características:

  1. Como o código da aplicação é visível ao público, é considerado um cliente público.
  2. O fluxo geral de autenticação de utilizador é o mesmo que discutimos na seção "OpenID Connect".
  3. A aplicação pode usar o token de ID emitido pelo provedor de identidade (ou seja, o provedor de OpenID Connect) para identificar o utilizador e exibir conteúdo específico do utilizador.
  4. Para manter a aplicação segura, a aplicação geralmente usa o fluxo de código de autorização com PKCE (Prova de Chave para Troca de Código) para a autenticação do utilizador e para obter tokens.

Normalmente, as SPAs precisam de aceder a outros serviços de API para busca e atualização de dados. Discutiremos isso na seção "Protege a tua API".

Aplicações móveis

A aplicação corre num dispositivo móvel (iOS, Android, etc.) e comunica-se com o servidor via APIs. Na maioria dos casos, estas aplicações têm as mesmas características das SPAs.

Aplicações de máquina-para-máquina (M2M)

Aplicações máquina-para-máquina são clientes que correm num servidor (máquina) e comunicam-se com outros servidores. Estas aplicações têm as seguintes características:

  1. Assim como aplicações web a correr num servidor, aplicações M2M são clientes privados.
  2. A aplicação pode não precisar de identificar o utilizador; em vez disso, precisa de autenticar-se para aceder a outros serviços.
  3. A aplicação pode usar o token de acesso emitido pelo provedor de identidade (ou seja, o provedor de OAuth 2.0) para aceder a outros serviços.
  4. Para manter a aplicação segura, a aplicação geralmente usa o fluxo de credenciais do cliente para obter tokens de acesso.

Ao aceder a outros serviços, a aplicação pode precisar de fornecer o token de acesso no cabeçalho da solicitação. Discutiremos isto na seção "Protege a tua API".

Aplicações a correr em dispositivos IoT

A aplicação corre num dispositivo IoT (por exemplo, dispositivos domésticos inteligentes, wearables, etc.) e comunica-se com o servidor via APIs. Estas aplicações têm as seguintes características:

  1. Dependendo da capacidade do dispositivo, pode ser um cliente público ou privado.
  2. O fluxo geral de autenticação pode ser diferente do que discutimos na seção "OpenID Connect" de acordo com a capacidade do dispositivo. Por exemplo, alguns dispositivos podem não ter uma tela para os utilizadores inserirem suas credenciais.
  3. Se o dispositivo não precisar de identificar o utilizador, pode não precisar de usar tokens de ID ou pontos de extremidade de userinfo; em vez disso, pode ser tratado como uma aplicação de máquina-para-máquina (M2M).
  4. Para manter a aplicação segura, a aplicação pode usar o fluxo de código de autorização com PKCE (Prova de Chave para Troca de Código) para autenticação de utilizador e obter tokens ou o fluxo de credenciais do cliente para obter tokens de acesso.

Ao comunicar com o servidor, o dispositivo pode precisar de fornecer o token de acesso no cabeçalho da solicitação. Discutiremos isto na seção "Protege a tua API".

Protege a tua API

Com o OpenID Connect, é possível identificar o utilizador e buscar dados específicos do utilizador através de ID tokens ou pontos de extremidade de userinfo. Este processo é chamado de autenticação. Mas provavelmente não queres expor todos os teus recursos a todos os utilizadores autenticados, por exemplo, apenas administradores podem aceder à página de gestão de utilizadores.

Aqui é onde a autorização entra em jogo. Lembra que o OAuth 2.0 é uma estrutura de autorização, e o OpenID Connect é uma camada de identidade em cima do OAuth 2.0; o que significa que podes também usar o OAuth 2.0 quando o OpenID Connect já está em vigor.

Vamos relembrar o exemplo que usamos na seção "OAuth 2.0": MyApp quer acessar o Google Drive do utilizador. Não é prático deixar a MyApp aceder a todos os arquivos do utilizador no Google Drive. Em vez disso, a MyApp deve reivindicar explicitamente o que quer aceder (por exemplo, acesso de leitura a arquivos em uma pasta específica). Nos termos do OAuth 2.0, isso é chamado de escopo.

Podes ver o termo "permissão" usado de forma intercambiável com "escopo" no contexto do OAuth 2.0, pois às vezes "escopo" é ambíguo para utilizadores não técnicos.

Quando o utilizador concede acesso à MyApp, o servidor de autorização emite um token de acesso com o escopo solicitado. O token de acesso é então enviado para o servidor de recursos (Google Drive) para acessar os arquivos do utilizador.

Naturalmente, podemos substituir o Google Drive pelos nossos próprios serviços de API. Por exemplo, a MyApp precisa de acessar o OrderService para buscar o histórico de pedidos do utilizador. Desta vez, como a autenticação do utilizador já é feita pelo provedor de identidade e tanto a MyApp quanto o OrderService estão sob nosso controle, podemos pular a etapa de pedir ao utilizador para conceder acesso; a MyApp pode enviar diretamente a solicitação para o OrderService com o token de acesso emitido pelo provedor de identidade.

O token de acesso pode conter um read:order escopo para indicar que o utilizador pode ler o seu histórico de pedidos.

Agora, digamos que o utilizador entra acidentalmente num URL de página de administrador no navegador. Como o utilizador não é um administrador, não há escopo admin no token de acesso. O OrderService rejeitará a solicitação e retornará uma mensagem de erro.

Neste caso, o OrderService pode retornar um código de status 403 Forbidden para indicar que o utilizador não está autorizado a aceder à página de administrador.

Para aplicações de máquina-para-máquina (M2M), nenhum utilizador está envolvido no processo. As aplicações podem solicitar diretamente tokens de acesso do provedor de identidade e usá-los para aceder a outros serviços. O mesmo conceito aplica-se: o token de acesso contém os escopos necessários para aceder aos recursos.

Design de autorização

Podemos ver duas coisas importantes a considerar ao projetar autorização para proteger os teus serviços de API:

  1. Escopos: Define o que o cliente pode aceder. Os escopos podem ser detalhados (por exemplo, read:order, write:order) ou mais gerais (por exemplo, order) dependendo dos teus requisitos.
  2. Controle de acesso: Define quem pode ter escopos específicos. Por exemplo, apenas administradores podem ter o escopo admin.

Sobre controle de acesso, algumas abordagens populares são:

  • Controle de acesso baseado em funções (RBAC): Atribui funções aos utilizadores e define que funções podem aceder a que recursos. Por exemplo, uma função de administrador pode aceder à página de administrador.
  • Controle de acesso baseado em atributos (ABAC): Define políticas com base em atributos (por exemplo, departamento do utilizador, localização, etc.) e toma decisões de controle de acesso com base nestes atributos. Por exemplo, um utilizador do departamento de "Engenharia" pode aceder à página de engenharia.

Vale a pena mencionar que para ambas as abordagens, a maneira padrão para verificar o controle de acesso é verificar os escopos dos tokens de acesso, em vez de funções ou atributos. Funções e atributos podem ser muito dinâmicos e os escopos são mais estáticos, o que facilita muito a gestão.

Para informações detalhadas sobre controle de acesso, podes consultar RBAC e ABAC: Os modelos de controle de acesso que deves conhecer.

Tokens de acesso

Embora tenhamos mencionado o termo "token de acesso" várias vezes, não discutimos como obter um. No OAuth 2.0, um token de acesso é emitido pelo servidor de autorização (provedor de identidade) após um fluxo de autorização bem-sucedido.

Vamos dar uma olhada mais de perto no exemplo do Google Drive e assumir que estamos a usar o fluxo de código de autorização:

Há alguns passos importantes no fluxo para emissão de token de acesso:

  • Passo 2 (Redirecionar para o Google): A MyApp redireciona o utilizador para o Google com uma solicitação de autorização. Normalmente, esta solicitação inclui as seguintes informações:
    • Qual cliente (MyApp) está a iniciar a solicitação
    • Quais escopos a MyApp está a solicitar
    • Onde o Google deve redirecionar o utilizador após a autorização estar concluída
  • Passo 5 (Pedir permissão para acessar o Google Drive): O Google pede ao utilizador para conceder acesso à MyApp. O utilizador pode escolher conceder ou negar acesso. Este passo é chamado de consentimento.
  • Passo 7 (Redirecionar para MyApp com dados de autorização): Este passo é recentemente introduzido no diagrama. Em vez de retornar o token de acesso diretamente, o Google retorna um código de autorização de uso único para a MyApp para uma troca mais segura. Este código é usado para obter o token de acesso.
  • Passo 8 (Usar código de autorização para trocar por token de acesso): Este também é um novo passo. A MyApp envia o código de autorização para o Google para trocar por um token de acesso. Como provedor de identidade, o Google compõe o contexto da solicitação e decide se emite ou não um token de acesso:
    • O cliente (MyApp) é quem alega ser
    • O utilizador concedeu acesso ao cliente
    • O utilizador é quem alega ser
    • O utilizador possui os escopos necessários
    • O código de autorização é válido e não expirou

O exemplo acima assume que o servidor de autorização (provedor de identidade) e o servidor de recursos são os mesmos (Google). Se forem separados, tomando o exemplo de MyApp e OrderService, o fluxo será assim:

Neste fluxo, o servidor de autorização (IdP) emite tanto um ID token quanto um token de acesso à MyApp (passo 8). O ID token é usado para identificar o utilizador, e o token de acesso é usado para aceder a outros serviços como OrderService. Como tanto a MyApp quanto o OrderService são serviços de primeira parte, geralmente não pedem ao utilizador para conceder acesso; em vez disso, confiam no controle de acesso no provedor de identidade para determinar se o utilizador pode aceder aos recursos (ou seja, se o token de acesso contém os escopos necessários).

Finalmente, vamos ver como o token de acesso é usado em aplicações de máquina-para-máquina (M2M). Como nenhum utilizador está envolvido no processo e a aplicação é confiável, ela pode solicitar diretamente um token de acesso do provedor de identidade:

O controle de acesso ainda pode ser aplicado aqui. Para o OrderService, não importa quem é o utilizador ou qual aplicação está a solicitar os dados; importa apenas o token de acesso e os escopos que ele contém.

Tokens de acesso geralmente são codificados como JSON Web Tokens (JWT). Para aprender mais sobre JWT, podes consultar O que é JSON Web Token (JWT)?.

Indicadores de recursos

OAuth 2.0 introduz o conceito de escopos para controle de acesso. No entanto, podes rapidamente perceber que os escopos não são suficientes:

  • OpenID Connect define um conjunto de escopos padrão como openid, offline_access e profile. Pode ser confuso misturar esses escopos padrão com os teus escopos personalizados.
  • Podes ter vários serviços de API que compartilham o mesmo nome de escopo, mas têm significados diferentes.

Uma solução comum é adicionar sufixos (ou prefixos) aos nomes dos escopos para indicar o recurso (serviço de API). Por exemplo, read:order e read:product são mais claros do que read e read. Uma extensão do OAuth 2.0 RFC 8707 introduz um novo parâmetro resource para indicar o servidor de recursos que o cliente quer aceder.

Na realidade, serviços de API geralmente são definidos por URLs, então é mais natural usar URLs como indicadores de recursos. Por exemplo, a API do OrderService pode ser representada como:

Como podes ver, o parâmetro traz a conveniência de usar os URLs reais de recursos nas solicitações de autorização e tokens de acesso. Vale a pena mencionar que o RFC 8707 pode não ser implementado por todos os provedores de identidade. Deves verificar a documentação do teu provedor de identidade para ver se ele suporta o parâmetro resource (Logto o suporta).

Em resumo, o parâmetro resource pode ser usado em solicitações de autorização e tokens de acesso para indicar o recurso que o cliente deseja aceder.

Não há restrições sobre a acessibilidade dos indicadores de recursos, isto é, o indicador de recurso não precisa ser um URL real que aponta para um serviço de API. Assim, o nome "indicador de recurso" reflete adequadamente o seu papel no processo de autorização. Podes usar URLs virtuais para representar recursos que desejas proteger. Por exemplo, podes definir um URL virtual https://api.example.com/admin na tua aplicação monolítica para representar o recurso que apenas administradores podem aceder.

Juntando tudo

Neste ponto, cobrimos o básico do OAuth 2.0 e OpenID Connect, e como usá-los em diferentes tipos de aplicação e cenários. Embora os tenhamos discutido separadamente, podes combiná-los de acordo com os teus requisitos de negócio. A arquitetura geral pode ser tão simples quanto isto:

Ou um pouco mais complexa:

À medida que a tua aplicação cresce, verás que o provedor de identidade (IdP) desempenha um papel crítico na arquitetura; mas isso não se relaciona diretamente aos objetivos do teu negócio. Embora seja uma ótima ideia transferi-lo para um fornecedor confiável, precisamos escolher o provedor de identidade sabiamente. Um bom provedor de identidade pode simplificar muito o processo, reduzir o esforço de desenvolvimento e salvar-te de possíveis armadilhas de segurança.

Notas finais

Para aplicações modernas em nuvem, o provedor de identidade (ou servidor de autorização) é o ponto central para autenticação de utilizadores, gestão de identidade e controle de acesso. Embora tenhamos discutido muitos conceitos neste post, ainda há muitas nuances a considerar ao implementar tal sistema. Se estiveres interessado em aprender mais, podes navegar pelo nosso blog para artigos mais aprofundados.