Saltar a contenido

OAuth Discovery

Endpoints de descubrimiento OAuth 2.0 que permiten a clientes MCP descubrir automaticamente como autenticarse.

Ubicacion: api_gateway/http_server.py

RFCs Implementados

RFC Endpoint Proposito
RFC 9728 /.well-known/oauth-protected-resource Descubrir authorization server
RFC 8414 /.well-known/oauth-authorization-server Metadata OAuth completa
MCP Spec /.well-known/mcp.json Manifest del servidor MCP
OpenAPI 3.0 /.well-known/openapi.json Spec REST API (ChatGPT Actions)

Flujo de Discovery

Cliente MCP (Claude Code, ChatGPT, Gemini)
    |
    1. POST /mcp (sin auth)
    |
    v
Server responde 401 + WWW-Authenticate:
    Bearer resource_metadata="/.well-known/oauth-protected-resource"
    |
    2. GET /.well-known/oauth-protected-resource
    |
    v
Server responde:
    {
        "resource": "https://mcp.tu-dominio.com",
        "authorization_servers": ["https://tu-tenant.us.auth0.com"],
        "scopes_supported": ["openid", "profile", "email", "offline_access"],
        "bearer_methods_supported": ["header"]
    }
    |
    3. GET /.well-known/oauth-authorization-server  (ChatGPT especifico)
    |
    v
Server hace proxy del openid-configuration de Auth0:
    {
        "issuer": "https://tu-tenant.us.auth0.com/",
        "authorization_endpoint": "https://tu-tenant.us.auth0.com/authorize",
        "token_endpoint": "https://tu-tenant.us.auth0.com/oauth/token",
        "registration_endpoint": "https://tu-tenant.us.auth0.com/oidc/register",
        ...
    }
    |
    4. Login en Auth0 (navegador)
    |
    5. POST /mcp con Authorization: Bearer <jwt>
    |
    v
Server valida JWT (audience con/sin barra + issuer), extrae el email
del claim https://gdilatam.com/email, construye MCPContext, procesa tool call

Endpoints de Discovery

/.well-known/oauth-protected-resource (RFC 9728)

Indica a clientes MCP donde autenticarse.

{
    "resource": "https://mcp.tu-dominio.com",
    "authorization_servers": [
        "https://tu-tenant.us.auth0.com"
    ],
    "scopes_supported": [
        "openid",
        "profile",
        "email",
        "offline_access"
    ],
    "bearer_methods_supported": ["header"]
}

/.well-known/oauth-authorization-server (RFC 8414)

Metadata completa del authorization server. Proxy del endpoint de Auth0:

GET /.well-known/oauth-authorization-server
    -> Proxy a https://tu-tenant.us.auth0.com/.well-known/openid-configuration
    -> Modifica registration_endpoint para DCR

ChatGPT

ChatGPT requiere especificamente /.well-known/oauth-authorization-server (no el path de OpenID). Tambien acepta el alias /.well-known/oauth-authorization-server/mcp.

/.well-known/mcp.json

Manifest del servidor MCP:

{
    "name": "GDI-Latam MCP Server",
    "version": "1.0.0",
    "description": "Servidor MCP para gestion documental gubernamental",
    "tools": ["search_cases", "get_case", "search_documents", ...],
    "authentication": {
        "type": "oauth2",
        "discovery": "/.well-known/oauth-protected-resource"
    }
}

/.well-known/openapi.json

Especificacion OpenAPI 3.0 para la REST API. Usado por ChatGPT Actions:

{
    "openapi": "3.0.0",
    "info": {
        "title": "GDI-Latam REST API",
        "version": "1.0.0"
    },
    "paths": {
        "/api/v1/cases/search": { ... },
        "/api/v1/documents/search": { ... }
    }
}

Requisitos Auth0 para MCP

Para que el flujo OAuth de MCP funcione, el tenant Auth0 necesita cuatro cosas. Si falta alguna, el cliente termina con un access token opaco (no-JWT) y el gateway responde 401 con "Issuer no valido".

1. API (resource server) creada

Un resource server cuyo identifier sea la URL publica del gateway (MCP_RESOURCE_URI), firmado con RS256. Ejemplo DEV: https://gdi-gateway-dev.fly.dev.

2. DCR habilitado

Settings > Advanced > OIDC Dynamic Application Registration (tenant flag enable_dynamic_client_registration = true). Los clientes MCP (Claude Code, claude.ai, etc.) se auto-registran por Dynamic Client Registration.

3. Conexion de BD a Domain Level

La Database Connection (Username-Password-Authentication) promovida a Domain Level (is_domain_connection = true). Es requisito de DCR.

4. Client-grant default para clientes third-party

Los clientes que crea DCR son third-party (prefijo tpc_). Por defecto NO pueden acceder a la API del gateway: Auth0 corta con Client is not authorized to access resource server y el cliente cae a un token opaco. Hay que crear un client-grant default para todos los third-party sobre la API:

POST /api/v2/client-grants
{
  "audience": "<MCP_RESOURCE_URI>",
  "default_for": "third_party_clients",
  "subject_type": "user",
  "allow_all_scopes": true
}

La barra final (RFC 8707)

Los clientes MCP canonicalizan el resource (RFC 8707 / RFC 9728): a un host sin path le agregan /. Por eso piden el token para https://<gateway>/ (CON barra final), aunque el discovery anuncie la URL sin barra.

Consecuencia: en Auth0 tiene que existir un resource server con ese identifier exacto (con barra) mas su client-grant third-party. Como los identifiers de Auth0 son inmutables, se registran las dos formas (con y sin /), cada una con su grant. El gateway acepta el audience con y sin barra (_get_mcp_valid_audiences).

Audiences JWT soportados

El gateway solo acepta tokens emitidos contra su propia Custom API (MCP_RESOURCE_URI), con o sin barra final. NO acepta el audience del Management API (https://<tenant>.auth0.com/api/v2/).

# _get_mcp_valid_audiences() en api_gateway/auth_mcp.py
VALID_AUDIENCES = [
    MCP_RESOURCE_URI,          # p.ej. https://gdi-gateway-dev.fly.dev
    MCP_RESOURCE_URI + "/",    # variante canonicalizada por el cliente
]

Email del usuario: claim namespaced

El access token de un cliente third-party sale con scope offline_access unicamente (sin openid). Por eso no trae el claim email estandar y no sirve para /userinfo (devuelve 401). El email del usuario viaja en un claim con namespace que agrega un Action de Auth0:

  • https://gdilatam.com/email
  • https://gdilatam.com/name

El gateway lo lee con _email_from_payload (con fallback al claim email estandar).


Respuesta 401 del MCP

Cuando un cliente envia un request sin token, el server responde:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.tu-dominio.com/.well-known/oauth-protected-resource"
Content-Type: application/json

{
    "error": "Authorization required",
    "hint": "Use OAuth 2.0 to authenticate"
}

El header WWW-Authenticate con resource_metadata es la clave para que clientes MCP descubran automaticamente el flujo OAuth.


Troubleshooting

Error Causa Solucion
"Authorization required" No hay token OAuth Usar cliente MCP con OAuth
"Token invalido" JWT expirado o mal formado Re-autenticar via OAuth
"multi_tenant_selection_required" Usuario con multiples organizaciones Especificar tenant_id
"Audience no valido" Token con audience incorrecto Verificar audience en Auth0
Cliente recibe token opaco (no-JWT) / "Issuer no valido" Falta el client-grant default_for: third_party_clients en la API Crear el client-grant third-party (ver Requisitos Auth0 #4)
Client is not authorized to access resource server El resource pedido (con barra final) no existe en Auth0 o no tiene grant Registrar el resource server con barra https://<gateway>/ + su grant
access_denied: Service not found: https://<gateway>/ No hay API con el identifier con barra final Crear el resource server con barra final
"Usuario no encontrado" Email no existe en BD (o no se pudo leer el claim namespaced) Verificar usuario en la organizacion y el Action que agrega https://gdilatam.com/email
Discovery no funciona Endpoint no accesible Verificar CORS y rutas en http_server.py