Problemas frecuentes con OAuth
Cómo resolver los errores más habituales al integrar el inicio de sesión con UTILIA: direcciones de vuelta, PKCE, invalid_grant, CORS, permisos y límites de uso.
Actualizado el . 8 min de lectura
Esta página recoge los errores que más aparecen al integrar OAuth con UTILIA OS, con su causa y su solución. Busca el código de error (invalid_grant, invalid_client...) o el síntoma. Los errores de OAuth llegan siempre con error y error_description: el segundo dice la causa exacta, salvo cuando falla la autenticación del cliente al pedir o comprobar tokens (invalid_client), que por seguridad no la revela.
¿Dónde falla tu integración?
¿En qué momento aparece el error?
Al pedir la autorización:
¿Qué ves?
La página de error:
¿Qué error muestra la página de error de UTILIA OS?
invalid_redirect_uri:
La dirección de vuelta no coincide
Se compara carácter por carácter con las registradas para el cliente.invalid_client:
Cliente inexistente o desactivado
Con Client not found o Client is inactive.
invalid_request:
Si el error_description habla de code_challenge: falta, no usa S256 o no tiene el formato.login_required:
prompt=none no renueva en silencio
Sin sesión de UTILIA OS en el navegador, responde siempre así. Usa el refresh token.Un aviso al consentir:
Le faltan permisos, su plan no incluye esos scopes o ha desactivado todos los del servidor MCP.
Al pedir o renovar tokens:
¿Qué responde POST /api/oauth/token?
invalid_grant:
¿Canjeabas un código o renovabas?
Canjeaba un código:
Código, PKCE o dirección de vuelta
El código vale 60 segundos y una sola vez. El error_description dice qué comprobación falla.Renovaba:
Refresh token anulado o caducado
Con has been rotated, usaste uno ya anulado; con has expired, han pasado los 90 días desde la autorización.
invalid_client:
Revisa las credenciales del cliente
El client_id no existe o está desactivado, falta la clave secreta o no es correcta.429:
Espera lo que diga Retry-After
Este endpoint admite 60 peticiones por minuto y dirección IP.Llega sin id_token:
Solo llega en el canje del código y si pediste openid; nunca al renovar.
Al llamar con el token:
¿Qué responde la llamada?
401:
¿Ha pasado una hora desde que recibiste el access token?
Sí:
Dura 1 hora. Renueva con el refresh token.No:
¿Qué responde al renovar?
invalid_client:
Desactivar el cliente deja sin validez todos sus tokens, aunque no hayan caducado.invalid_grant:
Con El usuario ya no puede renovar este acceso, su cuenta ya no está activa. Pídele de nuevo la autorización.
Lo bloquea el navegador:
403 CORS_ORIGIN_NOT_ALLOWED
Añádelo en los dominios autorizados de tu aplicación externa.
Al cerrar la sesión:
invalid_request
Su firma se verifica en RS256; también cuentan iss, el cliente activo y la dirección de vuelta registrada.
La dirección de vuelta no coincide
Síntoma: al iniciar la autorización, la persona ve la página de error de UTILIA OS con invalid_redirect_uri; o, al canjear el código, recibes invalid_grant con redirect_uri mismatch.
La redirect_uri se compara carácter por carácter con las registradas para el cliente y, en el canje, con la que usaste en la autorización.
| Registrada | Enviada | Resultado |
|---|---|---|
https://miapp.com/callback | http://miapp.com/callback | Falla: cambia el protocolo |
http://localhost:3000/callback | http://localhost:3001/callback | Falla: cambia el puerto |
https://miapp.com/callback | https://www.miapp.com/callback | Falla: cambia el dominio |
http://localhost:3000/callback | http://localhost:3000/callback/ | Falla: sobra la barra final |
http://localhost:3000/callback | http://localhost:3000/callback | Correcto |
En producción las direcciones tienen que usar https; solo localhost, 127.0.0.1 y [::1] admiten http. Para añadir una, edita las direcciones de vuelta en Activar OAuth en tu aplicación o registra de nuevo el cliente.
invalid_grant
El error_description te dice la causa:
error_description | Causa y solución |
|---|---|
Authorization code has expired | El código vale 60 segundos. Repite la autorización y canjéalo enseguida |
Authorization code has already been used | Cada código sirve una vez y, si se reutiliza, se revocan los tokens emitidos con él. Revisa que tu aplicación no canjee dos veces, por ejemplo al recargar la página de vuelta |
PKCE verification failed | El code_verifier no corresponde al code_challenge. Comprueba que usas el par de la misma autorización |
Invalid code_verifier format | El code_verifier no tiene entre 43 y 128 caracteres del conjunto [A-Za-z0-9-._~] |
redirect_uri mismatch | Ver el apartado anterior |
Refresh token has been rotated | Has usado un refresh token ya anulado. Ver Mi refresh token ha dejado de funcionar |
Refresh token has expired | Han pasado los 90 días desde la autorización. La persona tiene que volver a autorizar |
The granted scopes are no longer available for this user | La persona ha perdido los permisos de todo lo que concedía el token. Vuelve a pedir la autorización |
El usuario ya no puede renovar este acceso | La cuenta de la persona ya no está activa |
Errores de PKCE
- Base64 en lugar de Base64URL: cambia
+por-y/por_, y quita los=del final. - Método
plain: solo se admiteS256. code_challengeolvidado: la autorización sin él se rechaza. En Authlib, declaracode_challenge_method: "S256"enclient_kwargs.code_verifierperdido entre la redirección y la vuelta: guárdalo en la sesión de tu servidor o ensessionStorage, no en una variable en memoria que se pierde al recargar la página.
Implementación de referencia:
import { randomBytes, createHash } from "node:crypto";
const codeVerifier = randomBytes(32).toString("base64url");
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");Mi refresh token ha dejado de funcionar
Síntoma: invalid_grant con Refresh token has been rotated.
Cada renovación entrega un refresh token nuevo y anula el anterior. Si se vuelve a usar uno anulado, UTILIA OS lo trata como un posible robo y revoca toda la cadena de esa autorización. Las causas habituales:
- Tu aplicación guarda el refresh token una vez y no lo actualiza tras renovar.
- Dos procesos o pestañas renuevan con el mismo refresh token, uno detrás del otro.
- Una renovación llegó a hacerse, pero la respuesta se perdió por un corte de red, y tu aplicación reintenta con el refresh token viejo.
Solución: guarda siempre el último refresh token, serializa las renovaciones y, cuando recibas este error, borra lo guardado y vuelve a pedir la autorización. El patrón completo está en Rotación del refresh token.
El access token deja de valer a la hora
Es lo esperado: el access token dura 1 hora y el refresh token 90 días desde la autorización. Renueva con el refresh token cuando el access token esté a punto de caducar o cuando recibas un 401; si la renovación falla con invalid_grant, vuelve a pedir la autorización. Los SDK oficiales lo hacen solos.
invalid_client
En POST /api/oauth/token y POST /api/oauth/introspect, todos los fallos de autenticación del cliente responden lo mismo, 401 con Client authentication failed, para no revelar si un client_id existe. Si lo recibes, comprueba:
- Que el
client_idesté completo y sea el de esta aplicación. - Que el cliente siga activo y su registro no haya vencido. Desactivar OAuth en el panel lo desactiva: actívalo de nuevo y usa las credenciales nuevas.
- Que envíes la clave secreta si el cliente es de tipo Con servidor propio, y que sea la correcta.
- En la introspección, que el cliente sea confidencial: un cliente público no puede usarla.
error_description | Causa y solución |
|---|---|
Client authentication failed | Cualquiera de los casos de la lista anterior |
Missing client_id | La petición no lleva client_id |
Client mismatch | El código o el refresh token se emitieron para otro cliente |
Al iniciar la autorización, la página de error de UTILIA OS sí distingue Client not found (el client_id no existe) de Client is inactive (el cliente está desactivado), y no te devuelve a tu aplicación.
Desactivar el cliente deja sin validez todos sus tokens al momento, aunque no hayan caducado: las llamadas con ellos responden 401, la introspección los da por inactivos (active: false) y las renovaciones fallan con invalid_client (Client authentication failed).
CORS al llamar desde el navegador
Síntoma: el navegador bloquea la llamada a https://os.utilia.ai/api/... desde tu web, o recibes 403 con CORS_ORIGIN_NOT_ALLOWED.
Añade el dominio de tu web (por ejemplo https://miapp.com) en la ficha de tu aplicación externa, en Ajustes › Avanzado › Dominios autorizados. Admite comodines como https://*.tu-dominio.com. El cambio tarda hasta un minuto en aplicarse. Las llamadas desde un servidor (Node.js, Python...) no pasan por CORS.
La persona no puede autorizar
- «Acceso no autorizado»: a la persona no le queda ningún permiso que conceder entre los que pides. Pide menos scopes o pide al administrador de su organización que le dé los permisos de UTILIA OS que correspondan.
- «Tu organización no tiene contratado el aplicativo necesario para conceder este acceso»: el plan de su organización no incluye el aplicativo de esos scopes. Solo se resuelve cambiando el plan.
- «Con esta selección, tu aplicación no podría usar...»: la persona ha desactivado todos los permisos del servidor MCP que conectas. Tiene que dejar activado al menos uno.
Para ver qué puede conceder una persona, la pantalla de consentimiento usa GET /api/oauth/user/scopes con su sesión. Los conjuntos y scopes están en Scopes y recursos.
Una persona desactivada
Si la cuenta de la persona deja de estar activa en UTILIA OS, sus tokens se rechazan aunque no hayan caducado y las renovaciones fallan; si se da de baja, además se revocan. Cuando vuelva a tener acceso, pídele de nuevo la autorización.
No llega el id_token
- No pediste
openid: inclúyelo enscope, por ejemploopenid profile email. - Estás renovando: el
id_tokensolo llega en el canje del código, no en las renovaciones. Guárdalo la primera vez o pide los datos aGET /api/oauth/userinfo.
prompt=none siempre devuelve login_required
El navegador no envía la sesión de UTILIA OS al servidor de autorización, así que una redirección con prompt=none responde siempre login_required. No sirve para renovar en silencio: usa el refresh token.
Límites de uso
Si superas un límite, recibes 429 Too Many Requests con la cabecera Retry-After, que dice cuántos segundos esperar antes de reintentar.
| Endpoint | Límite por dirección IP |
|---|---|
GET /.well-known/* | 600 por minuto |
POST /api/oauth/register | 30 por hora |
GET /api/oauth/authorize-request/:id | 60 por minuto |
POST /api/oauth/token | 60 por minuto |
POST /api/oauth/revoke | 60 por minuto |
POST /api/oauth/introspect | 120 por minuto |
GET /api/oauth/scopes | 60 por minuto |
GET /api/oauth/clients/:clientId | 30 por minuto |
GET y POST /api/oauth/logout | 30 por minuto |
Los documentos de descubrimiento cambian muy poco: guárdalos en caché en lugar de pedirlos en cada inicio de sesión.