Fase 5 6 horas Avançado

A Grande Corrida

Na reta final, só passa quem tem credencial. OAuth 2.0, OIDC e JWT são os portões da segurança digital — saber abri-los é chegar primeiro.

Fase 5 — A Grande Corrida · OAuth 2.0, OIDC e JWT
Progresso: 0/8 (0%)

Objetivos da Fase

O que você vai aprender:

  • OAuth 2.0: fluxo de autorização passo a passo
  • OIDC (OpenID Connect): identidade sobre OAuth
  • Estrutura de um JWT (header.payload.signature)
  • Scopes, claims e o princípio do menor privilégio
  • Diferença entre autenticação e autorização
  • Provedores OIDC: Google, GitHub, Microsoft
  • Biblioteca python-jose para JWT em Python

O que você vai criar:

  • Decodificador de JWT manual (base64url)
  • Simulador do fluxo OAuth 2.0
  • Validador de token OIDC com python-jose
  • Dashboard HTTP com rota protegida

🏰 Convite ao Reino OIDC

Para reforçar OAuth 2.0, OIDC e JWT com uma narrativa complementar, visite o Reino OIDC antes de seguir para o projeto da fase.

Entrar no Reino OIDC

5.1 — O Portão da Corrida: OAuth 2.0

Imagina a largada: o fiscal pede sua credencial, você apresenta sua identidade, ele emite um crachá temporário. Esse crachá é o access token.

🔑 Fluxo Authorization Code

  1. Usuário clica em "Entrar com Google"
  2. App redireciona para o provedor com client_id + scope
  3. Usuário autentica e autoriza
  4. Provedor retorna um authorization_code
  5. App troca o código por access_token + id_token
  6. App usa o token para acessar recursos protegidos
# Simulador do fluxo OAuth 2.0 em Python
import secrets
import urllib.parse

CLIENT_ID = "circuito_ferradura_app"
REDIRECT_URI = "http://localhost:8080/callback"
SCOPE = "openid profile email"

state = secrets.token_urlsafe(16)  # proteção CSRF

params = {
    "response_type": "code",
    "client_id": CLIENT_ID,
    "redirect_uri": REDIRECT_URI,
    "scope": SCOPE,
    "state": state,
}
url = (
    "https://accounts.google.com/o/oauth2/auth?"
    + urllib.parse.urlencode(params)
)
print(f"→ Redirecionar para:\n{url}")

5.2 — O Número do Peito: JWT

Cada piloto tem um número no peito — único e verificável. O JWT funciona assim: três partes separadas por ponto, codificadas em base64url.

# Estrutura de um JWT
# HEADER.PAYLOAD.SIGNATURE
#
# Header  → {"alg": "RS256", "typ": "JWT"}
# Payload → {"sub": "12345", "name": "Ciclano",
#             "email": "ci@ferradura.dev", "iat": 1702930800}

import base64
import json

def decodificar_jwt(token: str) -> dict:
    partes = token.split(".")

    def decode_b64(valor: str) -> dict:
        padding = "=" * (-len(valor) % 4)
        dados = base64.urlsafe_b64decode(valor + padding)
        return json.loads(dados)

    header = decode_b64(partes[0])
    payload = decode_b64(partes[1])

    return {
        "header": header,
        "payload": payload,
        "assinatura": partes[2][:20] + "...",
    }

# Exemplo com python-jose (validação com chave pública)
from jose import jwt
claims = jwt.decode(
    token,
    chave_publica,
    algorithms=["RS256"],
    audience=CLIENT_ID,
)
print(f"Bem-vindo, {claims['name']}!")

5.3 — OIDC: A Identidade sobre o OAuth

OAuth diz "você pode acessar". OIDC responde "mas quem é você?". O id_token contém as claims de identidade verificáveis.

# Claims típicas de um id_token OIDC
import time

id_token_payload = {
    "iss": "https://accounts.google.com",  # quem emitiu
    "sub": "103456789012345678901",  # ID único do usuário
    "aud": CLIENT_ID,  # para quem é o token
    "exp": 1702934400,  # quando expira (Unix)
    "iat": 1702930800,  # quando foi emitido
    "name": "Ciclano da Corrida",
    "email": "ciclano@ferradura.dev",
    "email_verified": True,
}

def piloto_autenticado(claims: dict) -> bool:
    return (
        claims.get("email_verified") is True
        and claims["exp"] > time.time()
    )

✅ Checklist da Fase 5

🔐 Projeto da Fase 5

Crie um micro-servidor Flask com duas rotas: /login (redireciona para Google OAuth) e /dashboard (protegida — exige id_token válido na sessão). Use python-jose para validar o token e exiba o nome do piloto autenticado.

🔒 LGPD — Usamos localStorage para salvar progresso.