Na Parte I vimos como OIDC resolve o caos de senhas múltiplas de forma simples e elegante. Agora é hora de colocar a mão na massa: fluxos técnicos, código real, validação de tokens e integração com pipelines de CI/CD.
Se você é desenvolvedor, administrador de sistema ou arquiteto de soluções, esta parte complementa a visão de negócio com implementação prática.
OIDC é uma camada de identidade sobre OAuth 2.0. Enquanto OAuth 2.0 trata de autorização (acesso a recursos via Access Tokens), OIDC adiciona autenticação: quem é o usuário?
Fluxo Authorization Code + PKCE (detalhado):
code_verifier aleatório e deriva code_challenge (SHA256 + base64url)authorization_endpoint com code_challengecode para redirect_uricode + code_verifier por tokens no token_endpointiss, aud, exp) usando JWKSComponentes-chave:
sub, email, name, roles)Implementação completa de OIDC com FastAPI, usando Keycloak como IdP.
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.responses import RedirectResponse
from authlib.integrations.starlette_client import OAuth
from starlette.middleware.sessions import SessionMiddleware
import secrets
import hashlib
import base64
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="your-secret-key")
# Configuração OIDC
oauth = OAuth()
oauth.register(
name='keycloak',
client_id='seu-client-id',
client_secret='seu-client-secret',
issuer='https://keycloak.example.com/realms/seu-realm',
client_kwargs={
'scope': 'openid profile email roles'
}
)
def generate_pkce():
"""Gera code_verifier e code_challenge para PKCE"""
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8').rstrip('=')
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode('utf-8')).digest()
).decode('utf-8').rstrip('=')
return code_verifier, code_challenge
@app.get('/login')
async def login(request: Request):
code_verifier, code_challenge = generate_pkce()
request.session['code_verifier'] = code_verifier
redirect_uri = str(request.base_url) + 'callback'
return await oauth.keycloak.authorize_redirect(
request,
redirect_uri,
code_challenge=code_challenge,
code_challenge_method='S256'
)
@app.get('/callback')
async def callback(request: Request):
code_verifier = request.session.pop('code_verifier', None)
if not code_verifier:
raise HTTPException(400, "Invalid session")
# Troca code por tokens
token = await oauth.keycloak.authorize_access_token(
request,
code_verifier=code_verifier
)
# Extrai e valida ID Token
id_token = token.get('id_token')
if not id_token:
raise HTTPException(400, "No ID token received")
# Parse claims do ID Token
user_info = token.get('userinfo') or {}
request.session['user'] = {
'sub': user_info.get('sub'),
'email': user_info.get('email'),
'name': user_info.get('name'),
'roles': user_info.get('roles', [])
}
return RedirectResponse('/dashboard')
def get_current_user(request: Request):
"""Dependency para rotas protegidas"""
user = request.session.get('user')
if not user:
raise HTTPException(401, "Not authenticated")
return user
@app.get('/dashboard')
async def dashboard(user = Depends(get_current_user)):
return {"message": f"Welcome {user['name']}", "roles": user['roles']}
@app.get('/logout')
async def logout(request: Request):
request.session.clear()
logout_url = "https://keycloak.example.com/realms/seu-realm/protocol/openid-connect/logout"
return RedirectResponse(f"{logout_url}?redirect_uri={request.base_url}")
Quando você precisa validar ID Tokens sem biblioteca de alto nível:
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
// Cliente JWKS para buscar chaves públicas
const client = jwksClient({
jwksUri: 'https://login.example.com/.well-known/jwks.json',
cache: true,
cacheMaxAge: 600000, // 10 min
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
if (err) return callback(err);
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
async function validateIdToken(idToken, expectedAudience, expectedIssuer) {
return new Promise((resolve, reject) => {
jwt.verify(idToken, getKey, {
audience: expectedAudience,
issuer: expectedIssuer,
algorithms: ['RS256'],
}, (err, decoded) => {
if (err) return reject(err);
// Validações adicionais
const now = Math.floor(Date.now() / 1000);
if (decoded.exp < now) {
return reject(new Error('Token expired'));
}
if (decoded.iat > now + 300) { // 5 min de tolerância
return reject(new Error('Token issued in the future'));
}
resolve(decoded);
});
});
}
// Uso em middleware Express
app.use('/protected', async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid Authorization header' });
}
const idToken = authHeader.slice(7);
try {
const claims = await validateIdToken(
idToken,
'seu-client-id',
'https://login.example.com'
);
req.user = claims;
next();
} catch (error) {
res.status(401).json({ error: error.message });
}
});
Configuração completa para assumir Role AWS via OIDC no GitHub Actions:
1. Trust Policy da Role AWS:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": [
"repo:sua-org/seu-repo:ref:refs/heads/main",
"repo:sua-org/seu-repo:ref:refs/heads/develop",
"repo:sua-org/seu-repo:pull_request"
]
}
}
}
]
}
2. Workflow GitHub Actions:
name: Deploy to AWS
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
permissions:
id-token: write # Necessário para OIDC
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: sa-east-1
role-session-name: GitHubActions-${{ github.run_id }}
- name: Verify AWS identity
run: |
aws sts get-caller-identity
echo "AWS_ACCOUNT_ID: $(aws sts get-caller-identity --query Account --output text)"
- name: Deploy infrastructure
run: |
# CDK, Terraform, CloudFormation, etc.
echo "Deploying to environment: ${{ github.ref_name }}"
- name: Run tests
if: github.event_name == 'pull_request'
run: |
echo "Running integration tests"
3. Script para criar OIDC Provider (se não existir):
#!/bin/bash
# create-oidc-provider.sh
THUMBPRINT="6938fd4d98bab03faadb97b34396831e3780aea1"
PROVIDER_URL="https://token.actions.githubusercontent.com"
aws iam create-open-id-connect-provider \
--url $PROVIDER_URL \
--thumbprint-list $THUMBPRINT \
--client-id-list sts.amazonaws.com
Validação do ID Token:
iss, aud, exp, iat e assinaturaPKCE obrigatório:
code_verifier: 43-128 chars, base64url sem paddingcode_challenge_method: sempre S256Scopes mínimos:
openid (obrigatório) + profile, email, rolesCI/CD seguro:
sub com repo/branch específicosrole-session-name para auditoriaMulti-tenant:
/.well-known/openid-configuration)iss baseada no tenantaud no ID Token não confere com client_idiss no ID Token não confere com esperadoexp passou ou iat no futuro (sincronize clocks)kid não encontradocode_challenge não confere com code_verifierOIDC elimina a complexidade de gerenciar identidades distribuídas. Com Authorization Code + PKCE, validação correta do ID Token e políticas bem definidas, você obtém:
Checklist de implementação:
Próximos passos: comece com um app piloto, meça a experiência do usuário e evolua com telemetria e políticas de segurança.
Este artigo complementa a Parte I com detalhes técnicos. Juntos, cobrem desde a visão de negócio até implementação prática.
A Cara Core Informática oferece consultoria e implementação de OIDC, incluindo suporte técnico aos finais de semana e feriados.
sub, iss, aud, exp, email, name, roles)openid, profile, email, roles)/.well-known/openid-configuration com configurações do IdPtoken_endpoint