Pure OAuth2/OIDC implementation following RFC 6749 for Keycloak and multiple identity providers
Project description
OIDC Pure - Manual Completo
Implementação pura em Python de OAuth2 (RFC 6749) e OpenID Connect para Keycloak e outros provedores de identidade. Sistema multi-provider genérico com suporte a Google, GitHub, Microsoft, Okta, Auth0 e qualquer provedor OIDC compatível.
📑 Índice
- Visão Geral
- Instalação
- Configuração Multi-Provider
- Guia de Uso Rápido
- Exemplos Práticos
- Arquitetura
- Configuração Keycloak
- Testes
- Referência da API
- Auditoria de Segurança
- Segurança
1. Visão Geral
1.1 Features
- ✅ OAuth2 RFC 6749 Completo: Implementação pura seguindo a especificação
- ✅ OIDC Discovery: Descoberta automática de endpoints
- ✅ PKCE (RFC 7636): Proof Key for Code Exchange para segurança
- ✅ Multi-Provider: Sistema genérico para qualquer provedor OIDC
- ✅ Authorization Code Flow: Fluxo completo com suporte a PKCE
- ✅ Client Credentials Flow: Para autenticação machine-to-machine
- ✅ Refresh Token Flow: Renovação de tokens
- ✅ JWT Validation: Validação de tokens (claims, expiração)
- ✅ UserInfo Endpoint: Obtenção de perfil do usuário
- ✅ httpx: Cliente HTTP moderno (sync e async)
- ✅ Type Hints: Completamente tipado para melhor suporte IDE
- ✅ Sem Banco de Dados: Operação stateless
1.2 Provedores Suportados
| Provedor | Status | Porta | Scopes | Notas |
|---|---|---|---|---|
| Keycloak | ✅ Testado | 5400 | openid profile email |
OIDC completo |
| Google OAuth | ✅ Testado | 5400 | openid profile email |
OIDC completo |
| GitHub OAuth | ✅ Testado | 5400 | user:email read:user |
OAuth2 puro* |
| Microsoft Azure AD | 🔧 Pronto | 5400 | openid profile email User.Read |
OIDC completo |
| Okta | 🔧 Pronto | 5400 | openid profile email |
OIDC completo |
| Auth0 | 🔧 Pronto | 5400 | openid profile email |
OIDC completo |
| Genérico | ✅ Qualquer OIDC | 5400 | Configurável | - |
*GitHub usa OAuth2 puro (não implementa OIDC Discovery). Endpoints são pré-configurados.
1.3 Estrutura do Projeto
.
├── oidc_pure/ # 📦 Biblioteca Principal
│ ├── __init__.py # API pública
│ ├── client.py # OIDCClient (interface principal)
│ ├── oauth2.py # Fluxos OAuth2 (RFC 6749)
│ ├── discovery.py # OIDC Discovery
│ ├── tokens.py # JWT validation e PKCE
│ ├── models.py # Modelos de dados
│ └── exceptions.py # Exceções customizadas
│
├── oidc_config.py # 🔧 Sistema Multi-Provider
├── .env # 🔒 Configurações (não versionado)
├── .env.example # 📝 Template de configuração
│
├── examples/ # 📚 Exemplos de Uso
│ ├── simple_example.py # Exemplo básico
│ ├── async_example.py # Exemplo assíncrono
│ ├── keycloak_example.py # Exemplo Keycloak completo
│ ├── flask_integration.py # Integração Flask
│ ├── fastapi_integration.py # Integração FastAPI (completo)
│ ├── django_integration.py # Integração Django 4.x/5.x (500+ linhas)
│ └── django_drf_integration.py # Integração DRF (700+ linhas)
│
├── tests/ # 🧪 Testes (213 testes, 99% cobertura)
│ ├── conftest.py # Configuração pytest
│ ├── test_client.py # Testes do cliente (24 testes, 100% cobertura)
│ ├── test_oauth2.py # Testes OAuth2 (86 testes, 97% cobertura)
│ ├── test_discovery.py # Testes discovery (25 testes, 100% cobertura)
│ ├── test_tokens.py # Testes JWT/PKCE (30 testes, 100% cobertura)
│ ├── test_models.py # Testes modelos (22 testes, 100% cobertura)
│ ├── test_integration.py # Testes integração
│ └── test_multi_provider.py # Testes multi-provider (26 testes)
│
└── README.md # Este arquivo
2. Instalação
2.1 Requisitos
- Python 3.12+
- httpx >= 0.27.0
- python-dotenv >= 1.2.1 (para multi-provider)
2.2 Instalação via uv (Recomendado)
# Clonar repositório
git clone https://github.com/seu-usuario/oidc_pure.git
cd oidc_pure
# Instalar dependências
uv sync
# Ativar ambiente virtual
source .venv/bin/activate # Linux/Mac
# ou
.venv\Scripts\activate # Windows
2.3 Instalação via pip
# Do diretório local
pip install -e .
# Do Git
pip install git+https://github.com/seu-usuario/oidc_pure.git
2.4 Instalação em Modo Desenvolvimento
# Para desenvolvimento ativo
cd /caminho/para/oidc_pure
pip install -e .
2.5 Componentes Opcionais
📦 Biblioteca Core (Obrigatório)
O diretório oidc/ contém a biblioteca principal e é obrigatório para uso em produção:
from oidc_pure import OIDCClient
# Uso direto da biblioteca core
client = OIDCClient(
issuer_url="https://accounts.google.com",
client_id="seu-client-id",
client_secret="seu-secret",
redirect_uri="http://localhost:5400/callback"
)
🔧 oidc_config.py (Opcional)
O arquivo oidc_config.py é um helper opcional que simplifica o uso multi-provider através de variáveis de ambiente:
from oidc_config import create_oidc_client
# Lê automaticamente do .env
client = create_oidc_client('google') # Muito mais simples!
Em produção: Se você não precisa gerenciar múltiplos provedores dinamicamente, pode remover o oidc_config.py e usar apenas o core oidc/.
⚠️ Impacto da remoção:
- ❌
test_manual.py- Parará de funcionar (usaoidc_config) - ❌
tests/test_multi_provider.py- 26 testes falharão - ❌
examples/fastapi_integration.py- Precisará refatoração - ❌
examples/django_integration.py- Pode usar core diretamente (exemplo inclui ambas abordagens) - ❌
examples/django_drf_integration.py- Pode usar core diretamente (exemplo inclui ambas abordagens) - ✅ Biblioteca core
oidc/- Continua funcionando normalmente - ✅ Exemplos
simple_example.py,async_example.py- Não são afetados
Recomendação: Mantenha o oidc_config.py se você precisa trocar entre provedores (Keycloak, Google, GitHub) facilmente. Remova apenas se vai usar um único provedor configurado diretamente no código.
3. Configuração Multi-Provider
3.1 Visão Geral do Sistema
O sistema multi-provider permite usar qualquer provedor OIDC sem mudar código, apenas alterando variáveis de ambiente.
Princípios:
- Provedor Ativo:
OIDC_PROVIDERdefine qual usar - Busca Específica: Procura
{PROVIDER}_*primeiro - Fallback Genérico: Se não encontrar, usa
OIDC_*
3.2 Configuração Inicial
# 1. Copiar template
cp .env.example .env
# 2. Gerar secret key
python -c "import secrets; print(secrets.token_hex(32))"
# 3. Editar .env com suas credenciais
3.3 Estrutura do .env
# =============================================================================
# PROVEDOR ATIVO
# =============================================================================
OIDC_PROVIDER=keycloak # keycloak, google, github, microsoft, okta, auth0
# =============================================================================
# CONFIGURAÇÃO GENÉRICA (Fallback)
# =============================================================================
OIDC_ISSUER_URL=https://seu-provedor.com/auth/realms/SEU_REALM
OIDC_CLIENT_ID=seu-client-id
OIDC_CLIENT_SECRET=seu-client-secret
OIDC_REDIRECT_URI=http://localhost:5400/callback
OIDC_SCOPES=openid profile email
# Configurações adicionais
OIDC_USE_PKCE=true
OIDC_VERIFY_SSL=true
OIDC_TOKEN_LEEWAY=60
# =============================================================================
# KEYCLOAK
# =============================================================================
KEYCLOAK_ISSUER_URL=https://sso.apps.alcoal.net.br/auth/realms/SUDES
KEYCLOAK_CLIENT_ID=seu-client-id
KEYCLOAK_CLIENT_SECRET=seu-client-secret
KEYCLOAK_REDIRECT_URI=http://localhost:5400/callback
KEYCLOAK_SCOPES=openid profile email
# =============================================================================
# GOOGLE OAUTH
# =============================================================================
# Console: https://console.cloud.google.com/apis/credentials
GOOGLE_ISSUER_URL=https://accounts.google.com
GOOGLE_CLIENT_ID=seu-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-seu-secret
GOOGLE_REDIRECT_URI=http://localhost:5400/callback
GOOGLE_SCOPES=openid profile email
# =============================================================================
# GITHUB OAUTH (GRATUITO) - OAuth2 puro, não OIDC
# =============================================================================
# Console: https://github.com/settings/developers
# 1. Acesse GitHub → Settings → Developer settings → OAuth Apps
# 2. Clique em "New OAuth App"
# 3. Configure Authorization callback URL: http://localhost:5400/callback
# NOTA: GitHub usa OAuth2 puro (não OIDC), endpoints são pré-configurados
GITHUB_ISSUER_URL=https://github.com
GITHUB_CLIENT_ID=seu-client-id
GITHUB_CLIENT_SECRET=seu-client-secret
GITHUB_REDIRECT_URI=http://localhost:5400/callback
GITHUB_SCOPES=user:email read:user
# =============================================================================
# MICROSOFT AZURE AD
# =============================================================================
# Portal: https://portal.azure.com/
# MICROSOFT_ISSUER_URL=https://login.microsoftonline.com/common/v2.0
# MICROSOFT_CLIENT_ID=seu-application-id
# MICROSOFT_CLIENT_SECRET=seu-client-secret
# MICROSOFT_REDIRECT_URI=http://localhost:5400/callback
# MICROSOFT_SCOPES=openid profile email User.Read
# =============================================================================
# APPLICATION CONFIGURATION
# =============================================================================
APP_HOST=localhost
APP_PORT=5400
APP_SECRET_KEY=gere-uma-chave-secreta-aleatoria
3.4 Como Funciona
# Sistema busca nesta ordem:
OIDC_PROVIDER=google
# 1. GOOGLE_ISSUER_URL (específico) ✅
# 2. OIDC_ISSUER_URL (fallback) se não encontrar
# Permite misturar:
GOOGLE_ISSUER_URL=... # Específico
OIDC_CLIENT_SECRET=... # Genérico (usado se GOOGLE_CLIENT_SECRET não existir)
3.5 Uso do Sistema Multi-Provider
from oidc_config import create_oidc_client, get_oidc_config
# 1. Usar provedor ativo do .env (OIDC_PROVIDER)
client = create_oidc_client()
# 2. Forçar provedor específico
client = create_oidc_client('google')
client = create_oidc_client('keycloak')
# 3. Obter configuração apenas
config = get_oidc_config('google')
print(f"Issuer: {config.issuer_url}")
print(f"Client ID: {config.client_id}")
# 4. Listar provedores disponíveis
from oidc_config import list_available_providers
providers = list_available_providers()
# {'keycloak': True, 'google': True, 'github': True, 'microsoft': False, ...}
# 5. Trocar de provedor em runtime
for provider in ['keycloak', 'google', 'github']:
client = create_oidc_client(provider)
# ... usar cliente
4. Guia de Uso Rápido
4.1 Exemplo Mínimo
from oidc_pure import OIDCClient
# 1. Inicializar cliente (discovery automático)
client = OIDCClient(
issuer_url="https://keycloak.example.com/realms/myrealm",
client_id="my-app",
client_secret="secret", # Opcional para clientes públicos
redirect_uri="http://localhost:5400/callback"
)
# 2. Obter URL de autorização
auth_url, state, code_verifier = client.get_authorization_url(
scope="openid profile email",
use_pkce=True
)
print(f"Visite: {auth_url}")
# 3. Após autorização, trocar código por token
token = client.handle_authorization_response(
response_url="http://localhost:5400/callback?code=...",
expected_state=state,
code_verifier=code_verifier
)
# 4. Obter informações do usuário
user_info = client.get_user_info(token.access_token)
print(f"Usuário: {user_info.name}")
print(f"Email: {user_info.email}")
4.2 Client Credentials Flow
from oidc_pure import OIDCClient
# Para autenticação machine-to-machine
client = OIDCClient(
issuer_url="https://keycloak.example.com/realms/myrealm",
client_id="service-account",
client_secret="secret"
)
# Obter token diretamente
token = client.client_credentials_grant(scope="api:read api:write")
print(f"Access Token: {token.access_token}")
print(f"Expira em: {token.expires_in}s")
4.3 Refresh Token
# Renovar access token expirado
new_token = client.refresh_token(token.refresh_token)
print(f"Novo Access Token: {new_token.access_token}")
4.4 Validação de Token
# Decodificar e validar JWT
claims = client.decode_token(token.id_token)
print(f"Subject: {claims['sub']}")
print(f"Issuer: {claims['iss']}")
print(f"Expira em: {claims['exp']}")
# Validar com verificação de assinatura (HMAC)
claims = client.validate_token(
token.access_token,
verify_signature=True
)
4.5 Uso Assíncrono
import asyncio
from oidc_pure import OIDCClient
async def main():
client = OIDCClient(
issuer_url="https://keycloak.example.com/realms/myrealm",
client_id="my-app",
redirect_uri="http://localhost:5400/callback"
)
# Operações assíncronas
token = await client.handle_authorization_response_async(
response_url=callback_url,
expected_state=state,
code_verifier=code_verifier
)
user_info = await client.get_user_info_async(token.access_token)
print(f"User: {user_info.name}")
asyncio.run(main())
5. Exemplos Práticos
📁 Todos os exemplos estão disponíveis em: examples/
Nota: Alguns exemplos podem requerer dependências adicionais:
- Flask:
pip install flask - FastAPI:
pip install fastapi uvicorn - Django:
pip install django - Django REST Framework:
pip install djangorestframework drf-spectacular
5.1 Aplicação Web Simples
"""Exemplo: Servidor web com callback OAuth"""
from http.server import HTTPServer, BaseHTTPRequestHandler
import webbrowser
from oidc_pure import OIDCClient
# Configuração
CLIENT = OIDCClient(
issuer_url="https://keycloak.example.com/realms/myrealm",
client_id="my-app",
client_secret="secret",
redirect_uri="http://localhost:5400/callback"
)
auth_data = {'code': None}
class CallbackHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path.startswith('/callback'):
auth_data['code'] = f"http://localhost:5400{self.path}"
self.send_response(200)
self.end_headers()
self.wfile.write(b"Autenticacao bem-sucedida!")
def log_message(self, *args):
pass
def authenticate():
auth_url, state, verifier = CLIENT.get_authorization_url(
scope="openid profile email",
use_pkce=True
)
webbrowser.open(auth_url)
server = HTTPServer(('localhost', 5400), CallbackHandler)
print("Aguardando autenticação...")
server.handle_request()
token = CLIENT.handle_authorization_response(
response_url=auth_data['code'],
expected_state=state,
code_verifier=verifier
)
user = CLIENT.get_user_info(token.access_token)
print(f"✅ Autenticado: {user.name} ({user.email})")
return token, user
if __name__ == "__main__":
authenticate()
5.2 Integração Flask
"""Exemplo: Integração com Flask"""
from flask import Flask, redirect, request, session
from oidc_pure import OIDCClient
import secrets
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
oidc_client = OIDCClient(
issuer_url="https://keycloak.example.com/realms/myrealm",
client_id="my-app",
client_secret="secret",
redirect_uri="http://localhost:5400/callback"
)
@app.route('/')
def index():
if 'user' in session:
return f"Olá, {session['user']['name']}!"
return '<a href="/login">Entrar</a>'
@app.route('/login')
def login():
auth_url, state, verifier = oidc_client.get_authorization_url(
scope="openid profile email",
use_pkce=True
)
session['state'] = state
session['verifier'] = verifier
return redirect(auth_url)
@app.route('/callback')
def callback():
token = oidc_client.handle_authorization_response(
response_url=request.url,
expected_state=session.get('state'),
code_verifier=session.get('verifier')
)
user_info = oidc_client.get_user_info(token.access_token)
session['user'] = {'name': user_info.name, 'email': user_info.email}
return redirect('/')
if __name__ == '__main__':
app.run(port=5400)
5.3 Integração com FastAPI
Para FastAPI, use dependências (Depends) para proteger rotas:
"""Exemplo simplificado - veja examples/fastapi_integration.py para versão completa"""
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer
from oidc_config import create_oidc_client
app = FastAPI()
security = HTTPBearer()
def get_current_user(credentials = Depends(security)):
"""Dependência para autenticação"""
token = credentials.credentials
# Validar token e retornar usuário
# (veja exemplo completo para implementação detalhada)
return user_info
@app.get("/api/protected")
def protected_route(user = Depends(get_current_user)):
return {"message": f"Olá, {user['name']}!"}
📖 Exemplo completo: examples/fastapi_integration.py
Inclui:
- Fluxo OAuth2 completo (login/callback/logout)
- Middleware de autenticação
- Proteção de rotas com
Depends() - Suporte a múltiplos provedores
- Documentação Swagger automática
5.4 Multi-Provider com Menu
"""Exemplo: Escolher provedor dinamicamente"""
from oidc_config import list_available_providers, create_oidc_client
def escolher_provedor():
providers = list_available_providers()
disponiveis = [name for name, ok in providers.items() if ok]
print("Provedores disponíveis:")
for i, name in enumerate(disponiveis, 1):
print(f" [{i}] {name.title()}")
escolha = int(input("Escolha: ")) - 1
return disponiveis[escolha]
def autenticar(provedor):
client = create_oidc_client(provedor)
auth_url, state, verifier = client.get_authorization_url(
scope="openid profile email",
use_pkce=True
)
print(f"Visite: {auth_url}")
callback = input("Cole a URL de callback: ")
token = client.handle_authorization_response(
response_url=callback,
expected_state=state,
code_verifier=verifier
)
user = client.get_user_info(token.access_token)
print(f"✅ {provedor.upper()}: {user.name}")
return token, user
provedor = escolher_provedor()
token, user = autenticar(provedor)
5.5 Integração com Django 4.x/5.x
A biblioteca pode ser integrada ao Django puro usando middleware, backends de autenticação customizados e decorators. Ideal para aplicações web tradicionais com templates e sessões.
# settings.py
OIDC_ISSUER_URL = os.getenv('OIDC_ISSUER_URL')
OIDC_CLIENT_ID = os.getenv('OIDC_CLIENT_ID')
OIDC_CLIENT_SECRET = os.getenv('OIDC_CLIENT_SECRET')
OIDC_REDIRECT_URI = 'http://localhost:8000/auth/callback'
AUTHENTICATION_BACKENDS = [
'myapp.backends.OIDCAuthenticationBackend', # Autenticação OIDC
'django.contrib.auth.backends.ModelBackend', # Admin padrão
]
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'myapp.middleware.OIDCAuthenticationMiddleware', # OIDC middleware
# ... outros middlewares
]
# views.py
from django.shortcuts import redirect
from django.contrib.auth.decorators import login_required
from oidc_pure import OIDCClient
oidc_client = OIDCClient(
issuer_url=settings.OIDC_ISSUER_URL,
client_id=settings.OIDC_CLIENT_ID,
client_secret=settings.OIDC_CLIENT_SECRET,
redirect_uri=settings.OIDC_REDIRECT_URI
)
def oidc_login(request):
"""Inicia fluxo OAuth2"""
state = secrets.token_urlsafe(32)
request.session['oidc_state'] = state
auth_url = oidc_client.get_authorization_url(state=state)
return redirect(auth_url)
def oidc_callback(request):
"""Processa callback OAuth2"""
code = request.GET.get('code')
state = request.GET.get('state')
# Validar state
if state != request.session.get('oidc_state'):
return JsonResponse({'error': 'Invalid state'}, status=400)
# Trocar code por tokens
token_response = oidc_client.exchange_code_for_tokens(code)
userinfo = oidc_client.get_userinfo(token_response.access_token)
# Autenticar via backend customizado
user = authenticate(
request=request,
access_token=token_response.access_token,
userinfo=userinfo.to_dict()
)
if user:
login(request, user)
request.session['oidc_access_token'] = token_response.access_token
return redirect('/')
return JsonResponse({'error': 'Authentication failed'}, status=401)
@login_required
def protected_view(request):
"""View protegida"""
return JsonResponse({
'message': 'Autenticado!',
'user': request.user.username
})
Recursos implementados:
- ✅ Backend de autenticação customizado (
OIDCAuthenticationBackend) - ✅ Middleware para validação de tokens em cada requisição
- ✅ Integração com sistema de sessões Django
- ✅ Decorator
@oidc_login_requiredpara views protegidas - ✅ Suporte a múltiplos providers (Keycloak, Google, GitHub)
📄 Exemplo completo: examples/django_integration.py (500+ linhas com middleware, backends, views, models e configurações)
5.6 Integração com Django REST Framework (DRF)
Para APIs REST, a integração é feita através de Authentication Classes e Permission Classes, permitindo autenticação via Bearer token. Recomendado para SPAs, mobile apps e microservices.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'api.authentication.OIDCAuthentication', # Autenticação customizada
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
# api/authentication.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from django.contrib.auth.models import User
from oidc_pure import OIDCClient
class OIDCAuthentication(BaseAuthentication):
"""
Autenticação via Bearer token OIDC.
Header: Authorization: Bearer <access_token>
"""
def __init__(self):
self.oidc_client = OIDCClient(
issuer_url=settings.OIDC_ISSUER_URL,
client_id=settings.OIDC_CLIENT_ID,
client_secret=settings.OIDC_CLIENT_SECRET,
redirect_uri=settings.OIDC_REDIRECT_URI
)
def authenticate(self, request):
"""Valida token e retorna (user, token)"""
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if not auth_header.startswith('Bearer '):
return None
access_token = auth_header[7:]
try:
# Validar token e obter userinfo
userinfo = self.oidc_client.get_userinfo(access_token)
# Buscar ou criar usuário
user, created = User.objects.get_or_create(
username=userinfo.sub,
defaults={
'email': userinfo.email or '',
'first_name': getattr(userinfo, 'given_name', '')[:30],
}
)
return (user, access_token)
except Exception as e:
raise AuthenticationFailed('Token inválido ou expirado')
# api/views.py
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated
from .authentication import OIDCAuthentication
class AuthViewSet(viewsets.ViewSet):
"""ViewSet para autenticação OIDC"""
@action(detail=False, methods=['get'], permission_classes=[AllowAny])
def login(self, request):
"""GET /api/auth/login - Retorna URL de autorização"""
state = secrets.token_urlsafe(32)
auth_url = oidc_client.get_authorization_url(state=state)
return Response({
'authorization_url': auth_url,
'state': state
})
@action(detail=False, methods=['post'], permission_classes=[AllowAny])
def token(self, request):
"""POST /api/auth/token - Troca code por tokens"""
code = request.data.get('code')
token_response = oidc_client.exchange_code_for_tokens(code)
return Response({
'access_token': token_response.access_token,
'refresh_token': token_response.refresh_token,
'expires_in': token_response.expires_in,
})
@action(detail=False, methods=['get'],
authentication_classes=[OIDCAuthentication],
permission_classes=[IsAuthenticated])
def userinfo(self, request):
"""GET /api/auth/userinfo - Dados do usuário autenticado"""
access_token = request.auth
userinfo = oidc_client.get_userinfo(access_token)
return Response(userinfo.__dict__)
# urls.py
from rest_framework.routers import DefaultRouter
from api.views import AuthViewSet
router = DefaultRouter()
router.register(r'auth', AuthViewSet, basename='auth')
urlpatterns = [
path('api/', include(router.urls)),
]
Recursos implementados:
- ✅
OIDCAuthenticationclass para validação automática de tokens - ✅ Integração com permissões DRF (
IsAuthenticated, custom permissions) - ✅ ViewSets para endpoints de autenticação (
/api/auth/login,/api/auth/token) - ✅ Suporte a refresh token (
/api/auth/refresh) - ✅ Cache de userinfo para performance
- ✅ Documentação OpenAPI/Swagger com
drf-spectacular
📄 Exemplo completo: examples/django_drf_integration.py (700+ linhas com authentication classes, permissions, serializers, ViewSets e testes)
Diferenças entre Django puro vs. DRF:
| Aspecto | Django Puro | Django REST Framework |
|---|---|---|
| Caso de uso | Apps web tradicionais | APIs REST (SPA, Mobile) |
| Autenticação | Sessões Django | Bearer tokens (stateless) |
| Implementação | Middleware + Backends | Authentication Classes |
| Views | Function/Class views | ViewSets + APIViews |
| Frontend | Templates Django | React/Vue/Angular |
| Sessões | Server-side sessions | Stateless (JWT) |
| Complexidade | Média | Baixa (mais direto) |
💡 Recomendação: Use Django puro para aplicações monolíticas com templates. Use DRF para APIs consumidas por SPAs ou apps mobile.
6. Arquitetura
6.1 Visão Geral
┌─────────────────────────────────────────────────────┐
│ OIDCClient │
│ (Interface de alto nível para aplicações) │
└────────────┬────────────────────────────────────────┘
│
├──────────┐
│ │
┌────────▼─────┐ ┌▼───────────────┐
│ OAuth2Flow │ │ TokenValidator │
│ (RFC 6749) │ │ (JWT) │
└────────┬─────┘ └────────────────┘
│
│
┌────────▼──────────┐
│ OIDCDiscovery │
│ (.well-known) │
└───────────────────┘
│
┌────▼────┐
│ httpx │
└─────────┘
6.1.1 Separação Modular
A biblioteca é dividida em:
📦 Core (Obrigatório) - oidc/
- Implementação pura OAuth2/OIDC
- Sem dependências de configuração externa
- Pode ser usado diretamente em produção
🔧 Helpers (Opcional) - oidc_config.py
- Facilita uso multi-provider
- Lê configurações do
.env - Útil para desenvolvimento e testes
- Pode ser removido em produção se não for necessário
# Produção: Uso direto (sem oidc_config.py)
from oidc_pure import OIDCClient
client = OIDCClient(issuer_url="...", client_id="...", ...)
# Desenvolvimento: Com helper multi-provider
from oidc_config import create_oidc_client
client = create_oidc_client('google')
6.2 Componentes
OIDCClient (client.py)
Interface principal para aplicações.
Métodos:
get_authorization_url(): Gera URL de autorizaçãohandle_authorization_response(): Troca código por tokenget_user_info(): Obtém perfil do usuáriorefresh_token(): Renova tokenvalidate_token(): Valida JWTclient_credentials_grant(): Fluxo M2M
OAuth2Flow (oauth2.py)
Implementação dos fluxos OAuth2 (RFC 6749).
Fluxos:
- Authorization Code
- Authorization Code + PKCE
- Client Credentials
- Refresh Token
OIDCDiscovery (discovery.py)
Descoberta automática de endpoints.
# GET {issuer}/.well-known/openid-configuration
config = discovery.get_configuration()
TokenValidator (tokens.py)
Validação e manipulação de JWT.
Funcionalidades:
- Decode base64url
- Validação de claims
- Verificação HMAC
- Geração PKCE
6.3 Fluxo de Autenticação
App → OIDCClient → Discovery → Keycloak
→ OAuth2Flow
→ TokenValidator
7. Configuração Keycloak
7.1 Criar Realm
- Acesse console Keycloak
- Create Realm → Nome:
myrealm - Save
7.2 Criar Cliente
- Clients → Create client
- Client ID:
my-app - Configure:
- Client authentication: ON
- Standard flow: ✅
- Redirect URIs:
http://localhost:5400/callback
- Copiar Client Secret (tab Credentials)
7.3 Criar Usuário
- Users → Add user
- Configure username, email
- Credentials → Set password
7.4 Configurar PKCE
- Cliente → Settings → Advanced
- Proof Key: S256
- Save
7.5 URLs
ISSUER_URL = "https://keycloak.example.com/realms/myrealm"
Endpoints descobertos automaticamente:
- Authorization:
/protocol/openid-connect/auth - Token:
/protocol/openid-connect/token - Userinfo:
/protocol/openid-connect/userinfo
7.6 Client Credentials (M2M)
- Novo cliente
- Configure:
- Client authentication: ON
- Service accounts roles: ON
- Standard flow: OFF
client = OIDCClient(
issuer_url="...",
client_id="service-account",
client_secret="secret"
)
token = client.client_credentials_grant()
8. Testes
8.1 Status
📈 Cobertura: 99%
✅ Passando: 213/221 (96%)
📦 Total: 213 testes (6 módulos com 100%)
8.2 Executar
# Todos os testes
uv run pytest
# Com cobertura
uv run pytest --cov=oidc_pure --cov-report=term-missing
# HTML report
uv run pytest --cov=oidc_pure --cov-report=html
# Específicos
uv run pytest tests/test_multi_provider.py -v
8.3 Cobertura por Módulo
Módulo Cobertura Testes
────────────────────────────────────────────
oidc/__init__.py 100% ✅ -
oidc/exceptions.py 100% ✅ -
oidc/discovery.py 100% ✅ 25 testes
oidc/client.py 100% ✅ 24 testes
oidc/models.py 100% ✅ 22 testes
oidc/tokens.py 100% ✅ 30 testes
oidc/oauth2.py 97% 🟢 86 testes
────────────────────────────────────────────
TOTAL 99% 🟢 213 testes
9. Referência da API
9.1 OIDCClient
client = OIDCClient(
issuer_url: str,
client_id: str,
client_secret: str | None = None,
redirect_uri: str | None = None,
scopes: str | list[str] = None,
use_pkce: bool = True,
verify_ssl: bool = True,
)
# Authorization
auth_url, state, verifier = client.get_authorization_url(
scope: str | list[str] | None = None,
use_pkce: bool = True,
)
# Token exchange
token = client.handle_authorization_response(
response_url: str,
expected_state: str,
code_verifier: str | None = None,
)
# User info
user_info = client.get_user_info(access_token: str)
# Refresh
new_token = client.refresh_token(refresh_token: str)
# Validation
claims = client.decode_token(token: str)
claims = client.validate_token(token: str, verify_signature: bool = True)
9.2 Multi-Provider
from oidc_config import (
get_oidc_config,
create_oidc_client,
list_available_providers,
)
# Get config
config = get_oidc_config(provider: str | None = None)
# Create client
client = create_oidc_client(provider: str | None = None)
# List providers
providers = list_available_providers()
# {'keycloak': True, 'google': True, 'github': True, 'microsoft': False, ...}
10. Auditoria de Segurança
10.1 Verificação de Vulnerabilidades (CVEs)
O projeto inclui um script automatizado de auditoria de segurança que verifica:
- ✅ Vulnerabilidades conhecidas (CVEs) em dependências
- ✅ Análise estática de código (SAST)
- ✅ Boas práticas de segurança
# Executar auditoria completa
./security_audit.sh
# Instalar ferramentas (primeira vez)
./security_audit.sh --install
# Análise completa com verificações extras
./security_audit.sh --full
# Modo CI/CD (falha se encontrar problemas)
./security_audit.sh --ci
10.2 Ferramentas Utilizadas
| Ferramenta | Descrição | Database |
|---|---|---|
| pip-audit | Verifica CVEs em dependências | PyPI Advisory Database |
| safety | Vulnerabilidades conhecidas | PyUp.io Safety DB |
| bandit | Análise estática de código (SAST) | Python security patterns |
10.3 Status Atual
Última auditoria: 2026-02-24
✅ Dependências diretas: 0 vulnerabilidades
✅ Dependências de teste: 0 vulnerabilidades
✅ Análise de código (SAST): 0 problemas
✅ Total de linhas auditadas: 1226
Resultado: ✅ APROVADO
10.4 Ambiente de Desenvolvimento
O projeto utiliza:
- uv - Gerenciador de pacotes Python rápido e moderno
- devcontainer - Ambiente de desenvolvimento isolado e reprodutível
- pytest - Framework de testes com 78% de cobertura
Todas as ferramentas de segurança são executadas dentro do ambiente virtual gerenciado pelo uv.
10.5 Relatório Completo
📄 Para relatório detalhado de segurança, veja: SECURITY.md
O relatório inclui:
- Status detalhado de cada dependência
- Práticas de segurança implementadas
- Recomendações para produção
- Processo de resposta a vulnerabilidades
- Como reportar problemas de segurança
10.6 CI/CD Integration
Para integrar a auditoria no seu pipeline:
# .github/workflows/security.yml
name: Security Audit
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Run Security Audit
run: ./security_audit.sh --ci
11. Segurança
10.1 Best Practices
1. Sempre Use PKCE
client.get_authorization_url(use_pkce=True) # ✅
2. Valide State (CSRF)
token = client.handle_authorization_response(
response_url=callback,
expected_state=session['state'], # ✅
code_verifier=verifier
)
3. Nunca Exponha Credenciais
# ❌ ERRADO
client_secret="abc123"
# ✅ CORRETO
from oidc_config import create_oidc_client
client = create_oidc_client() # Lê do .env
4. Valide Tokens
claims = client.decode_token(token)
assert claims['iss'] == expected_issuer
assert claims['exp'] > time.time()
5. Use HTTPS em Produção
# Dev: http://localhost:5400
# Prod: https://meuapp.com/callback ✅
10.2 Gerenciamento de Tokens
# ❌ ERRADO: localStorage
localStorage.setItem('token', token)
# ✅ CORRETO: Cookies HTTP-only
response.set_cookie(
'access_token',
value=token.access_token,
httponly=True, # Previne XSS
secure=True, # HTTPS only
samesite='Lax'
)
10.3 Checklist
□ PKCE habilitado
□ State validation
□ .env não versionado
□ HTTPS em produção
□ Cookies HTTP-only
□ Token lifespan curto
□ Refresh token rotation
□ MFA habilitado
□ Logs de auditoria
📚 Recursos
📄 Licença
MIT License - veja LICENSE.
🏆 Status
✅ Core OAuth2/OIDC: Completo
✅ Multi-Provider: Completo
✅ Keycloak: Testado
✅ Google OAuth: Testado
✅ GitHub OAuth: Testado
✅ Documentação: Completa
✅ Testes: 99% cobertura (213 testes)
✅ 6 módulos com 100% cobertura (client, discovery, models, tokens)
Pronto para produção com Keycloak, Google OAuth e GitHub OAuth.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oidc_pure-1.1.4.tar.gz.
File metadata
- Download URL: oidc_pure-1.1.4.tar.gz
- Upload date:
- Size: 75.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6af4255fd1552243a57c3289b84c3eef7f71fde4e0184127ca82303a1409740f
|
|
| MD5 |
4d5e4c8155f3f3da9fe20eda749421c2
|
|
| BLAKE2b-256 |
279ad980e9f1ae93e0d1a77bbfdb8ca9510300841658cd1740131570e7372d7a
|
File details
Details for the file oidc_pure-1.1.4-py3-none-any.whl.
File metadata
- Download URL: oidc_pure-1.1.4-py3-none-any.whl
- Upload date:
- Size: 27.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
562e7929500018bb24187408f20bfd41a64a03ba18502ffaa00d29fa12c798d0
|
|
| MD5 |
561b066cefe9cdd313c0312d121f0ea0
|
|
| BLAKE2b-256 |
3ca62b7f5fbdda371d155da2a033f4212738535e1ccb5b909c316f84c95e57ba
|