Pular para o conteúdo principal

Proteja seu FastAPI com RBAC (controle de acesso baseado em papel) e validação de JWT

Este guia irá ajudá-lo a implementar autorização para proteger suas APIs FastAPI usando controle de acesso baseado em papel (RBAC) e JSON Web Tokens (JWTs) emitidos pelo Logto.

Antes de começar​

Seus aplicativos cliente precisam obter tokens de acesso (Access tokens) do Logto. Se você ainda não configurou a integração do cliente, confira nossos Guias rápidos para React, Vue, Angular ou outros frameworks de cliente, ou veja nosso Guia máquina para máquina para acesso servidor a servidor.

Este guia foca na validação no lado do servidor desses tokens em seu aplicativo FastAPI.

Uma figura mostrando o foco deste guia

O que você vai aprender​

  • Validação de JWT: Aprenda a validar tokens de acesso (Access tokens) e extrair informações de autenticação (Authentication)
  • Implementação de middleware: Crie middleware reutilizável para proteção de API
  • Modelos de permissão: Entenda e implemente diferentes padrões de autorização (Authorization):
    • Recursos de API globais para endpoints de aplicação
    • Permissões de organização para controle de funcionalidades específicas do locatário
    • Recursos de API em nível de organização para acesso a dados multi-inquilino
  • Integração com RBAC: Implemente permissões e escopos baseados em papel (Role-based access control (RBAC)) em seus endpoints de API

Pré-requisitos​

  • Última versão estável do Python instalada
  • Compreensão básica de FastAPI e desenvolvimento de API web
  • Um aplicativo Logto configurado (veja Guias rápidos se necessário)

Visão geral dos modelos de permissão​

Antes de implementar a proteção, escolha o modelo de permissão que se encaixa na arquitetura do seu aplicativo. Isso está alinhado com os três principais cenários de autorização do Logto:

RBAC de recursos globais de API
  • Caso de uso: Proteger recursos de API compartilhados em todo o seu aplicativo (não específicos de organização)
  • Tipo de token: Token de acesso (Access token) com público global (global audience)
  • Exemplos: APIs públicas, serviços principais do produto, endpoints de administração
  • Melhor para: Produtos SaaS com APIs usadas por todos os clientes, microsserviços sem isolamento de locatário
  • Saiba mais: Proteger recursos globais de API

💡 Escolha seu modelo antes de prosseguir – a implementação fará referência à abordagem escolhida ao longo deste guia.

Passos rápidos de preparação​

Configure recursos e permissões do Logto​

  1. Criar recurso de API: Vá para Console → Recursos de API e registre sua API (ex: https://api.seuapp.com)
  2. Definir permissões: Adicione escopos como read:products, write:orders – veja Definir recursos de API com permissões
  3. Criar papéis globais: Vá para Console → Papéis e crie papéis que incluam as permissões da sua API – veja Configurar papéis globais
  4. Atribuir papéis: Atribua papéis a usuários ou aplicativos M2M que precisam de acesso à API
Novo no RBAC?:

Comece com nosso guia de controle de acesso baseado em papel para instruções passo a passo de configuração.

Atualize seu aplicativo cliente​

Solicite os escopos apropriados em seu cliente:

O processo geralmente envolve atualizar a configuração do seu cliente para incluir um ou mais dos seguintes:

  • Parâmetro scope nos fluxos OAuth
  • Parâmetro resource para acesso a recursos de API
  • organization_id para contexto de organização
Antes de codificar:

Certifique-se de que o usuário ou app M2M que você está testando recebeu os papéis ou papéis de organização adequados que incluam as permissões necessárias para sua API.

Inicialize seu projeto de API​

Para inicializar um novo projeto FastAPI, crie um diretório e configure a estrutura básica:

mkdir seu-nome-da-api
cd seu-nome-da-api

Crie um arquivo de requisitos:

requirements.txt
fastapi
uvicorn[standard]

Instale as dependências:

pip install -r requirements.txt

Crie uma aplicação FastAPI básica:

main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"message": "Hello from FastAPI"}

Inicie o servidor de desenvolvimento:

uvicorn main:app --reload
nota:

Consulte a documentação do FastAPI para mais detalhes sobre como configurar operações de rota, injeção de dependências e outros recursos.

Inicialize constantes e utilitários​

Defina as constantes e utilitários necessários em seu código para lidar com a extração e validação do token. Uma solicitação válida deve incluir um cabeçalho Authorization no formato Bearer <token de acesso (access token)>.

auth_middleware.py
JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks'
ISSUER = 'https://your-tenant.logto.app/oidc'

class AuthInfo:
def __init__(self, sub: str, client_id: str = None, organization_id: str = None,
scopes: list = None, audience: list = None):
self.sub = sub
self.client_id = client_id
self.organization_id = organization_id
self.scopes = scopes or []
self.audience = audience or []

def to_dict(self):
return {
'sub': self.sub,
'client_id': self.client_id,
'organization_id': self.organization_id,
'scopes': self.scopes,
'audience': self.audience
}

class AuthorizationError(Exception):
def __init__(self, message: str, status: int = 403):
self.message = message
self.status = status
super().__init__(self.message)

def extract_bearer_token_from_headers(headers: dict) -> str:
"""
Extrai o token bearer dos cabeçalhos HTTP.

Observação: FastAPI e Django REST Framework possuem extração de token integrada,
então esta função é principalmente para Flask e outros frameworks.
"""
authorization = headers.get('authorization') or headers.get('Authorization')

if not authorization:
raise AuthorizationError('Cabeçalho de autorização ausente', 401)

if not authorization.startswith('Bearer '):
raise AuthorizationError('O cabeçalho de autorização deve começar com "Bearer "', 401)

return authorization[7:] # Remove o prefixo 'Bearer '

Recupere informações sobre seu tenant Logto​

Você precisará dos seguintes valores para validar tokens emitidos pelo Logto:

  • URI do JSON Web Key Set (JWKS): A URL para as chaves públicas do Logto, usada para verificar assinaturas de JWT.
  • Emissor (Issuer): O valor esperado do emissor (URL OIDC do Logto).

Primeiro, encontre o endpoint do seu tenant Logto. Você pode encontrá-lo em vários lugares:

  • No Logto Console, em Configurações → Domínios.
  • Em qualquer configuração de aplicativo onde você configurou no Logto, Configurações → Endpoints & Credenciais.

Buscar no endpoint de descoberta do OpenID Connect​

Esses valores podem ser obtidos no endpoint de descoberta do OpenID Connect do Logto:

https://<seu-endpoint-logto>/oidc/.well-known/openid-configuration

Aqui está um exemplo de resposta (outros campos omitidos para brevidade):

{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}

Como o Logto não permite personalizar o URI do JWKS ou o emissor, você pode definir esses valores manualmente no seu código. No entanto, isso não é recomendado para aplicações em produção, pois pode aumentar a sobrecarga de manutenção caso alguma configuração mude no futuro.

  • URI do JWKS: https://<seu-endpoint-logto>/oidc/jwks
  • Emissor: https://<seu-endpoint-logto>/oidc

Valide o token e as permissões​

Após extrair o token e buscar a configuração OIDC, valide o seguinte:

  • Assinatura: O JWT deve ser válido e assinado pelo Logto (via JWKS).
  • Emissor (Issuer): Deve corresponder ao emissor do seu tenant Logto.
  • Público (Audience): Deve corresponder ao indicador de recurso da API registrado no Logto, ou ao contexto da organização se aplicável.
  • Expiração: O token não pode estar expirado.
  • Permissões (escopos) (Permissions (scopes)): O token deve incluir os escopos necessários para sua API / ação. Os escopos são strings separadas por espaço na reivindicação scope.
  • Contexto da organização: Se estiver protegendo recursos de API em nível de organização, valide a reivindicação organization_id.

Veja JSON Web Token para saber mais sobre a estrutura e reivindicações do JWT.

O que verificar para cada modelo de permissão​

As reivindicações e regras de validação diferem conforme o modelo de permissão:

  • Reivindicação de público (aud): Indicador de recurso de API
  • Reivindicação de organização (organization_id): Não presente
  • Escopos (permissões) a verificar (scope): Permissões do recurso de API

Para permissões de organização que não são de API, o contexto da organização é representado pela reivindicação aud (por exemplo, urn:logto:organization:abc123). A reivindicação organization_id só está presente para tokens de recursos de API em nível de organização.

dica:

Sempre valide tanto as permissões (escopos) quanto o contexto (público, organização) para APIs multi-tenant seguras.

Adicione a lógica de validação​

Utilizamos o PyJWT para validar JWTs. Instale-o caso ainda não tenha feito isso:

pip install pyjwt[crypto]

Primeiro, adicione estas utilidades compartilhadas para lidar com a validação de JWT:

jwt_validator.py
import jwt
from jwt import PyJWKClient
from typing import Dict, Any
from auth_middleware import AuthInfo, AuthorizationError, JWKS_URI, ISSUER

jwks_client = PyJWKClient(JWKS_URI)

def validate_jwt(token: str) -> Dict[str, Any]:
"""Validar JWT e retornar o payload"""
try:
signing_key = jwks_client.get_signing_key_from_jwt(token)

payload = jwt.decode(
token,
signing_key.key,
algorithms=['RS256'],
issuer=ISSUER,
options={'verify_aud': False} # Vamos verificar o público manualmente
)

verify_payload(payload)
return payload

except jwt.InvalidTokenError as e:
raise AuthorizationError(f'Token inválido: {str(e)}', 401)
except Exception as e:
raise AuthorizationError(f'Falha na validação do token: {str(e)}', 401)

def create_auth_info(payload: Dict[str, Any]) -> AuthInfo:
"""Criar AuthInfo a partir do payload do JWT"""
scopes = payload.get('scope', '').split(' ') if payload.get('scope') else []
audience = payload.get('aud', [])
if isinstance(audience, str):
audience = [audience]

return AuthInfo(
sub=payload.get('sub'),
client_id=payload.get('client_id'),
organization_id=payload.get('organization_id'),
scopes=scopes,
audience=audience
)

def verify_payload(payload: Dict[str, Any]) -> None:
"""Verificar payload com base no modelo de permissão"""
# Implemente sua lógica de verificação aqui com base no modelo de permissão
# Isso será mostrado na seção de modelos de permissão abaixo
pass

Em seguida, implemente o middleware para verificar o token de acesso:

auth_middleware.py
from fastapi import HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jwt_validator import validate_jwt, create_auth_info

security = HTTPBearer()

async def verify_access_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> AuthInfo:
try:
token = credentials.credentials
payload = validate_jwt(token)
return create_auth_info(payload)

except AuthorizationError as e:
raise HTTPException(status_code=e.status, detail=str(e))

De acordo com seu modelo de permissão, implemente a lógica de verificação apropriada em jwt_validator.py:

jwt_validator.py
def verify_payload(payload: Dict[str, Any]) -> None:
"""Verificar payload para recursos globais de API"""
# Verifique se a reivindicação de público corresponde ao seu indicador de recurso de API
audiences = payload.get('aud', [])
if isinstance(audiences, str):
audiences = [audiences]

if 'https://your-api-resource-indicator' not in audiences:
raise AuthorizationError('Público inválido')

# Verifique os escopos necessários para recursos globais de API
required_scopes = ['api:read', 'api:write'] # Substitua pelos escopos realmente necessários
scopes = payload.get('scope', '').split(' ') if payload.get('scope') else []
if not all(scope in scopes for scope in required_scopes):
raise AuthorizationError('Escopo insuficiente')

Aplique o middleware à sua API​

Agora, aplique o middleware às suas rotas de API protegidas.

app.py
from fastapi import FastAPI, Depends
from auth_middleware import verify_access_token, AuthInfo

app = FastAPI()

@app.get("/api/protected")
async def protected_endpoint(auth: AuthInfo = Depends(verify_access_token)):
# Acesse as informações de autenticação diretamente do parâmetro auth
return {"auth": auth.to_dict()}

Teste sua API protegida​

Obter tokens de acesso (Access tokens)​

Do seu aplicativo cliente: Se você configurou uma integração de cliente, seu aplicativo pode obter tokens automaticamente. Extraia o token de acesso e use-o nas requisições de API.

Para testes com curl / Postman:

  1. Tokens de usuário: Use as ferramentas de desenvolvedor do seu aplicativo cliente para copiar o token de acesso do localStorage ou da aba de rede.

  2. Tokens máquina para máquina: Use o fluxo de credenciais do cliente. Aqui está um exemplo não normativo usando curl:

    curl -X POST https://your-tenant.logto.app/oidc/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials" \
    -d "client_id=your-m2m-client-id" \
    -d "client_secret=your-m2m-client-secret" \
    -d "resource=https://your-api-resource-indicator" \
    -d "scope=api:read api:write"

    Pode ser necessário ajustar os parâmetros resource e scope de acordo com seu recurso de API e permissões; um parâmetro organization_id também pode ser exigido se sua API for voltada para organização.

dica:

Precisa inspecionar o conteúdo do token? Use nosso decodificador de JWT para decodificar e verificar seus JWTs.

Testar endpoints protegidos​

Requisição com token válido
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:3000/api/protected

Resposta esperada:

{
"auth": {
"sub": "user123",
"clientId": "app456",
"organizationId": "org789",
"scopes": ["api:read", "api:write"],
"audience": ["https://your-api-resource-indicator"]
}
}
Token ausente
curl http://localhost:3000/api/protected

Resposta esperada (401):

{
"error": "Authorization header is missing"
}
Token inválido
curl -H "Authorization: Bearer invalid-token" \
http://localhost:3000/api/protected

Resposta esperada (401):

{
"error": "Invalid token"
}

Testes específicos do modelo de permissão​

Cenários de teste para APIs protegidas com escopos globais:

  • Escopos válidos: Teste com tokens que incluam os escopos de API necessários (por exemplo, api:read, api:write)
  • Escopos ausentes: Espere 403 Proibido quando o token não tiver os escopos necessários
  • Público errado: Espere 403 Proibido quando o público não corresponder ao recurso de API
# Token sem escopos necessários - espera-se 403
curl -H "Authorization: Bearer token-without-required-scopes" \
http://localhost:3000/api/protected

Leitura adicional​

RBAC na prática: Implementando autorização segura para seu aplicativo

Construa um aplicativo SaaS multi-inquilino: Um guia completo do design à implementação