Canje, revocación e introspección de tokens
Canjea el código por tokens, renuévalos con el refresh token, revócalos y comprueba su estado con los endpoints token, revoke e introspect.
Actualizado el . 6 min de lectura
Cuando tienes el código de autorización, el resto de la vida de los tokens pasa por tres endpoints: POST /api/oauth/token para obtenerlos y renovarlos, POST /api/oauth/revoke para anularlos y POST /api/oauth/introspect para saber si un access token sigue vivo. Todos admiten el cuerpo en application/x-www-form-urlencoded (lo habitual en OAuth) o en JSON.
Canjear el código por tokens
/api/oauth/tokenCanjea el código de autorización por access token, refresh token e id_token
- grant_typestringobligatorio
- authorization_code.
- codestringobligatorio
- El código recibido en tu redirect_uri.
- redirect_uristringobligatorio
- La misma que usaste en la autorización, carácter por carácter.
- code_verifierstringobligatorio
- El code_verifier original (43 a 128 caracteres).
- client_idstringobligatorio
- En el cuerpo o, con client_secret_basic, en la cabecera Authorization: Basic.
- client_secretstring
- Solo si el cliente usa client_secret_post. Con client_secret_basic va en la cabecera.
curl -X POST https://os.utilia.ai/api/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code' \
-d 'code=CODIGO_RECIBIDO' \
-d 'redirect_uri=https://mi-app.com/callback' \
-d 'code_verifier=TU_CODE_VERIFIER' \
-d 'client_id=TU_CLIENT_ID'Respuesta
- access_tokenstringobligatorio
- Cadena opaca de 43 caracteres. Úsala como Bearer.
- token_typestringobligatorio
- Bearer.
- expires_innumberobligatorio
- 3600 (1 hora).
- refresh_tokenstringobligatorio
- Cadena opaca. Sirve para renovar sin volver a pedir consentimiento y cambia en cada uso.
- scopestring
- Scopes concedidos de verdad, separados por espacios. Pueden ser menos de los que pediste.
- id_tokenstring
- JWT firmado con RS256. Solo si se concedió openid.
{
"access_token": "TU_ACCESS_TOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "TU_REFRESH_TOKEN",
"scope": "openid crm:clients:view",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im9pZGMtcHJvZC0xIn0..."
}La firma del id_token se verifica con las claves de GET /api/oauth/jwks. Los claims están en Scopes y claims de OpenID Connect.
Errores del canje
error | error_description | Qué hacer |
|---|---|---|
invalid_request | code_verifier is required for PKCE (o falta code o redirect_uri) | Envía todos los campos |
invalid_grant | Authorization code has expired | El código vale 60 segundos: repite la autorización |
invalid_grant | Authorization code has already been used | Cada código sirve una vez; los tokens emitidos con él se han revocado |
invalid_grant | redirect_uri mismatch | Usa exactamente la dirección de la autorización |
invalid_grant | PKCE verification failed | El code_verifier no corresponde al code_challenge |
invalid_client | Client authentication failed | El client_id no existe, el cliente está desactivado o su registro venció, o falta el secreto de un cliente confidencial o no es correcto. Todos estos casos responden igual para no revelar si el cliente existe |
invalid_client | Client mismatch | El código se emitió para otro cliente |
Renovar con el refresh token
/api/oauth/tokenRenueva el access token con el refresh token
- grant_typestringobligatorio
- refresh_token.
- refresh_tokenstringobligatorio
- El último refresh token que recibiste.
- client_idstringobligatorio
- Identificador del cliente.
- client_secretstring
- Obligatorio para clientes confidenciales, igual que en el canje.
curl -X POST https://os.utilia.ai/api/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=refresh_token' \
-d 'refresh_token=TU_REFRESH_TOKEN' \
-d 'client_id=TU_CLIENT_ID'La respuesta trae un access token nuevo de 1 hora, un refresh token nuevo y el scope vigente. No trae id_token. El access token anterior deja de valer en ese momento, aunque no hubiera pasado su hora. El token renovado conserva el servidor al que estaba atado: el parámetro resource no lo cambia.
- El alcance solo puede encogerse. Si la persona ha perdido permisos, el token renovado los pierde también; si los gana, el token no crece: para ampliar el alcance hay que volver a autorizar.
- Si no queda ningún scope útil para el servidor del token, la renovación responde
invalid_grant(The granted scopes are no longer available for this user) y revoca la cadena. - Si la persona deja de estar activa en UTILIA OS, la renovación falla con
invalid_grant. - Si el cliente se ha desactivado, la renovación falla con
invalid_client(Client authentication failed).
Guarda siempre el refresh token más reciente
Cada renovación anula el refresh token que enviaste. Si vuelves a usar uno ya rotado, UTILIA OS lo trata como un posible robo, responde invalid_grant (Refresh token has been rotated) y revoca toda la cadena de esa autorización: la persona tendrá que volver a autorizar. El patrón correcto, con la concurrencia, está en Rotación del refresh token.
Revocar un token (RFC 7009)
/api/oauth/revokeRevoca un access token o un refresh token. Responde siempre 200.
- tokenstringobligatorio
- El access token o el refresh token que quieres anular.
- token_type_hintstring
- access_token o refresh_token. Con access_token solo se busca entre los access tokens.
curl -X POST https://os.utilia.ai/api/oauth/revoke \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'token=TU_REFRESH_TOKEN' \
-d 'token_type_hint=refresh_token'Responde 200 con {} aunque el token no exista o ya estuviera revocado, para no revelar nada sobre él. El access token y el refresh token de una misma emisión van juntos: revocar cualquiera de los dos anula ambos. Es lo que debes hacer cuando la persona cierra sesión en tu aplicación.
Comprobar un token (RFC 7662)
/api/oauth/introspectEstado y metadatos de un access token. Exige un cliente confidencial autenticado.
Solo la puede llamar un cliente confidencial (client_secret_basic o client_secret_post) que envíe su secreto, en la cabecera Authorization: Basic o en el cuerpo, como pide el RFC 7662. Un cliente público (none) no puede usarla y recibe 401 invalid_client, la misma respuesta que un cliente inexistente, desactivado o con el secreto incorrecto.
Además, solo puedes comprobar los tokens emitidos a tu propio cliente. El token de otro cliente responde active: false, igual que uno que no existe.
Si tu aplicación es pública (navegador, móvil, línea de órdenes o cliente MCP), no necesitas la introspección: cuando un access token deja de valer, la llamada que lo usa responde 401, y lo renuevas con el refresh token.
- tokenstringobligatorio
- El access token que quieres comprobar.
- token_type_hintstring
- Se acepta, pero no cambia nada: solo se reconocen access tokens. Un refresh token responde active: false.
curl -X POST https://os.utilia.ai/api/oauth/introspect \
-u 'TU_CLIENT_ID:TU_CLIENT_SECRET' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'token=TU_ACCESS_TOKEN'Si el token está vigente:
{
"active": true,
"scope": "openid crm:clients:view",
"client_id": "utilia_5e2a9c4d7b1f3e6a8c0d2f4b6a8e1c3d",
"username": "usuario@empresa.com",
"token_type": "Bearer",
"exp": 1714933600,
"iat": 1714930000,
"sub": "3b1f6c2e-8a4d-4f0b-9c7e-2d5a1e6f8b90",
"aud": "https://os.utilia.ai/api/mcp/crm",
"iss": "https://os.utilia.ai"
}aud es el servidor al que está atado el token. Si en la autorización no indicaste resource, vale https://os.utilia.ai/api/mcp.
La introspección solo responde active: true mientras el token está vigente. Estos son los caminos por los que deja de estarlo:
Cuándo deja de valer un access token
Vigente
La introspección responde active: true con sus metadatos.Se llega desde Vigente cuando: Se revoca, se renueva o la persona retira el acceso
Revocado
Estado final
Al renovar, el access token anterior deja de valer en el acto.Se llega desde Vigente cuando: Se desactiva el cliente al que pertenece
Rechazado
Estado final
Deja de valer aunque no conste como revocado.
Se llega desde Vigente cuando: Pasa 1 hora desde que se emitió
Caducado
Estado final
Se renueva con el refresh token.
Si la persona deja de estar activa en UTILIA OS, userinfo y los servidores MCP rechazan su token aunque siga vigente; si se da de baja, además se revoca.
Autenticación del cliente
El método se fija al registrar el cliente (token_endpoint_auth_method) y vale para el canje y la renovación. La introspección solo admite los dos métodos con secreto:
- noneclient_id en el cuerpo
- Clientes públicos: navegador, móvil, línea de órdenes o clientes MCP. Sin secreto; PKCE los protege. Es el método por defecto del registro dinámico. No admite la introspección.
- client_secret_basicAuthorization: Basic base64(client_id:client_secret)
- Clientes con servidor propio. Es el que usa la opción «Con servidor propio» del panel.
- client_secret_postclient_id y client_secret en el cuerpo
- Igual que el anterior, con las credenciales en el cuerpo.