# Cierre de sesión desde tu aplicación

Cómo cerrar la sesión de una persona que entró con UTILIA: revoca sus tokens y, si quieres, pásala por el endpoint de cierre de sesión de OpenID Connect.

Cuando una persona cierra sesión en tu aplicación, lo importante es que los tokens de UTILIA OS que guardabas dejen de valer. Esta página explica cómo hacerlo y cómo funciona el endpoint de cierre de sesión de OpenID Connect (_RP-Initiated Logout 1.0_), que UTILIA OS anuncia como `end_session_endpoint` en `/.well-known/openid-configuration`.

## Lo recomendado: revocar desde tu servidor

Al cerrar sesión, borra tu sesión local y revoca el refresh token con `POST /api/oauth/revoke`. Revocar cualquiera de los dos tokens de una emisión anula ambos, y el endpoint responde siempre `200`.

```bash
curl -X POST https://os.utilia.ai/api/oauth/revoke \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'token=REFRESH_TOKEN_DE_LA_PERSONA' \
  -d 'token_type_hint=refresh_token'
```

Los SDK oficiales lo hacen con `sdk.oauth.logout()` en JavaScript y `await sdk.oauth.logout()` en Python. El detalle de la revocación está en [Canje, revocación e introspección de tokens](https://os.utilia.ai/docs/developers/oauth/token-revoke-introspect#revocar-un-token-rfc-7009).

La sesión de la persona en UTILIA OS no se cierra: sigue abierta en su navegador, como ocurre con cualquier proveedor de inicio de sesión.

## El endpoint de cierre de sesión

**`GET /api/oauth/logout`**

Cierre de sesión iniciado por tu aplicación (OpenID Connect)

**`POST /api/oauth/logout`**

Igual que la variante GET, con los parámetros en el cuerpo

Admite hasta 30 peticiones por minuto.

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `id_token_hint` | `string (JWT)` | No | Un id_token emitido para tu aplicación. Identifica a la persona (sub) y al cliente (aud). Se acepta aunque haya caducado. |
| `post_logout_redirect_uri` | `string (URL)` | No | Adónde devolver a la persona. Tiene que estar registrada en el cliente. |
| `state` | `string` | No | Valor que vuelve sin cambios en la redirección, para relacionar la respuesta con tu petición. |

Primero comprueba el `id_token_hint`: su firma RS256 con la clave pública de `/api/oauth/jwks`, que lleve `sub`, `aud` e `iss`, que `iss` sea `https://os.utilia.ai` y que el cliente de `aud` exista y esté activo. Se acepta aunque haya caducado. Después, lo que hace depende de la sesión y de la dirección de vuelta:

**¿Qué hace /api/oauth/logout?**

¿Envías un id_token_hint válido?

- No lo envías: **Responde 200** (`{ logged_out: true }`). No revoca nada ni redirige.
- No es válido: **Página de error de UTILIA OS** (`invalid_request`). Con el motivo en error_description (firma, iss o cliente).
- Sí: ¿La petición lleva la sesión de UTILIA OS?
  - De otra persona: **Página de error de UTILIA OS** (`invalid_request`). «id_token_hint no coincide con la sesión actual».
  - De esa persona: Revoca todos los tokens de esa persona para tu cliente. ¿Indicaste una post_logout_redirect_uri?
    - Sí, registrada: **Vuelve a tu aplicación** (`302`). Con tu state, y los tokens ya revocados.
    - No: **Responde 200** (`{ logged_out: true }`). Con los tokens ya revocados.
  - Sin sesión: No revoca nada. ¿Indicaste una post_logout_redirect_uri?
    - Sí, registrada: **Vuelve a tu aplicación** (`302`). Con tu state, sin revocar ningún token.
    - No: **Página de error de UTILIA OS** (`invalid_request`). «post_logout_redirect_uri es obligatoria cuando no hay sesión activa».

_Sin la sesión de UTILIA OS no se revoca nada, para que un id_token filtrado no sirva para cerrar la sesión de otra persona._

Una dirección que no está en la lista del cliente lleva siempre a la página de error, con «post_logout_redirect_uri no está registrada para este cliente».

```
HTTP/1.1 302 Found
Location: https://mi-app.com/sesion-cerrada?state=corr-12345
```

> **Atención: Una redirección del navegador no revoca tokens**
>
> Cuando llevas a la persona a esta dirección con una redirección normal, el navegador no envía su sesión de UTILIA OS, así que se aplica la rama «Sin sesión»: el endpoint te devuelve a tu aplicación sin revocar nada. Para que tus tokens dejen de valer, revócalos desde tu servidor como se explica arriba.

Usa el `id_token` que recibiste al canjear el código; las renovaciones no traen uno nuevo, así que guárdalo.

```javascript
const params = new URLSearchParams({
  id_token_hint: idToken,
  post_logout_redirect_uri: "https://mi-app.com/sesion-cerrada",
  state: crypto.randomUUID(),
});
window.location.href = `https://os.utilia.ai/api/oauth/logout?${params}`;
```

## Registrar las direcciones de vuelta tras el cierre

Una `post_logout_redirect_uri` solo se acepta si está en `post_logout_redirect_uris` del cliente. Puedes declararlas en el registro dinámico:

```bash
curl -X POST https://os.utilia.ai/api/oauth/register \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "Mi app",
    "redirect_uris": ["https://mi-app.com/callback"],
    "post_logout_redirect_uris": ["https://mi-app.com/sesion-cerrada"]
  }'
```

- Hasta 10 direcciones, con `http` o `https`.
- No se admiten direcciones de redes privadas ni internas. Sí se admiten `localhost`, `127.0.0.1` y `::1`, que necesitan los clientes nativos.
- La comparación es exacta: `https://mi-app.com/` y `https://mi-app.com` son distintas.
- Si el cliente no tiene ninguna declarada, se acepta cualquiera de sus `redirect_uris`. Es una solución provisional: declara las tuyas.

## Errores

Si algo falla, la persona acaba en la página de error de UTILIA OS con `error=invalid_request` y uno de estos motivos en `error_description`:

| `error_description` | Causa |
| - | - |
| `id_token_hint inválido` | No es un JWT o su firma no se verifica con la clave pública de UTILIA OS |
| `id_token_hint incompleto` | Le falta `sub` o `aud` |
| `iss del id_token_hint no coincide` | Falta `iss` o lo emitió otro servidor |
| `Cliente del id_token_hint no activo` | El cliente de `aud` no existe o está desactivado |
| `id_token_hint no coincide con la sesión actual` | La sesión de la petición es de otra persona |
| `post_logout_redirect_uri es obligatoria cuando no hay sesión activa` | Sin sesión hay que indicar adónde volver |
| `post_logout_redirect_uri no está registrada para este cliente` | La dirección no está en la lista del cliente |

## Qué usar en cada caso

| Quieres | Usa |
| - | - |
| Cerrar la sesión en tu aplicación y anular los tokens que guardabas | `POST /api/oauth/revoke` desde tu servidor |
| Pasar a la persona por el cierre de sesión estándar y devolverla a tu aplicación | `GET /api/oauth/logout` con `post_logout_redirect_uri` |
| Que la persona retire el acceso a tu aplicación | Lo hace ella en Mi perfil › IA y aplicaciones |
