← Voltar para publicações Implementação técnica OIDC: código Python, JavaScript e GitHub Actions (Cara Core)

OIDC na prática — exemplos técnicos e implementação (Parte II)

Logo Cara Core Cara Core Informática 84 seguidores
27 de outubro de 2025
Tempo estimado de leitura: ~12 minutos

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.

Revisão: o que é OIDC (foco técnico)

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):

  1. Cliente gera code_verifier aleatório e deriva code_challenge (SHA256 + base64url)
  2. Redireciona usuário para authorization_endpoint com code_challenge
  3. Usuário autentica no IdP (MFA, políticas, consentimentos)
  4. IdP redireciona com code para redirect_uri
  5. Cliente troca code + code_verifier por tokens no token_endpoint
  6. Cliente valida ID Token (assinatura, iss, aud, exp) usando JWKS

Componentes-chave:

Exemplo 1: Login Web com Python (FastAPI)

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}")

Exemplo 2: Validação manual de ID Token (JavaScript)

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 });
  }
});

Exemplo 3: GitHub Actions → AWS via OIDC (sem chaves)

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

Boas práticas e pegadinhas

Validação do ID Token:

PKCE obrigatório:

Scopes mínimos:

CI/CD seguro:

Multi-tenant:

Troubleshooting comum

Conclusão técnica

OIDC 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.

Hashtags

#OIDC #OAuth2 #JWT #PKCE #JWKS #SSO #Autenticação #Autorização #GitHub #AWS #CI #CD #Segurança #CaraCoreInformática

Contato

🤝 Gostou do conteúdo?
Conecte-se conosco no LinkedIn para mais conteúdos sobre desenvolvimento e inovação tecnológica!
Seguir no LinkedIn