# Scopes y claims de OpenID Connect

Qué datos de la persona recibes en el id_token y en /api/oauth/userinfo según los scopes openid, profile y email, y cómo verificarlos.

OpenID Connect es la capa de identidad sobre OAuth: con ella tu aplicación sabe quién ha iniciado sesión. UTILIA OS te lo dice de dos formas, con el mismo contenido: el `id_token`, un JWT firmado que llega al canjear el código, y el endpoint `GET /api/oauth/userinfo`. Lo que incluyen depende de tres scopes.

## Scopes de identidad

| Scope | Claims que añade |
| - | - |
| `openid` | Hace que se emita el `id_token` y da `sub`, el identificador de la persona, y `org_id`, el de su organización |
| `profile` | `name`, `given_name`, `family_name` y `picture` (solo si la persona tiene foto) |
| `email` | `email` y `email_verified` |

No dependen de los permisos de la persona ni del plan de su organización, y en la pantalla de consentimiento no se pueden desactivar. Si no pides ningún scope y el registro de tu cliente no define otros, se piden estos tres.

## Claims del `id_token`

| Claim | Tipo | Scope | Descripción |
| - | - | - | - |
| `iss` | `string` | `openid` | Emisor: `https://os.utilia.ai` |
| `sub` | `string` | `openid` | Identificador de la persona (UUID). Es estable: úsalo como clave en tu base de datos |
| `aud` | `string` | `openid` | El `client_id` de tu aplicación |
| `exp` | `number` | `openid` | Caducidad, una hora después de la emisión (segundos desde 1970) |
| `iat` | `number` | `openid` | Momento de emisión |
| `at_hash` | `string` | `openid` | Resumen del access token emitido a la vez |
| `nonce` | `string` | `openid` | El `nonce` que enviaste en la autorización, si lo enviaste |
| `org_id` | `string` | `openid` | Identificador de la organización de la persona |
| `name` | `string` | `profile` | Nombre completo |
| `given_name` | `string` | `profile` | Nombre |
| `family_name` | `string` | `profile` | Apellidos |
| `picture` | `string` | `profile` | Dirección de la foto de perfil |
| `email` | `string` | `email` | Correo |
| `email_verified` | `boolean` | `email` | `true` solo si la persona confirmó ese correo en UTILIA OS |

```json
{
  "iss": "https://os.utilia.ai",
  "sub": "3b1f6c2e-8a4d-4f0b-9c7e-2d5a1e6f8b90",
  "aud": "utilia_5e2a9c4d7b1f3e6a8c0d2f4b6a8e1c3d",
  "exp": 1743638400,
  "iat": 1743634800,
  "at_hash": "Qx2mN8pL4vR7tY1wZ5kB3c",
  "nonce": "n-0S6_WzA2Mj",
  "org_id": "cm9x2k4b70001qz8l5f3h6t2v",
  "name": "María García",
  "given_name": "María",
  "family_name": "García",
  "email": "maria@ejemplo.example",
  "email_verified": true
}
```

El `id_token` solo llega en el canje del código, no en las renovaciones con el refresh token. Si necesitas datos actualizados de la persona más adelante, pídelos a `userinfo`.

## Lo que los claims no dicen

> **Atención: No decidas el acceso con estos claims**
>
> `org_id` dice de qué organización es la persona y `email_verified` solo vale `true` si confirmó su correo, pero el correo solo es único dentro de una organización: dos personas de empresas distintas pueden tener el mismo. Identifica cada cuenta por `sub` (y, si te importa la empresa, por `org_id`), nunca por el correo ni por su dominio, y si enlazas cuentas por correo exige además `email_verified: true`.

Un cliente creado con el [registro dinámico](https://os.utilia.ai/docs/developers/oauth/quickstart) lo puede autorizar cualquier persona con cuenta en UTILIA OS, sea de la organización que sea. Si tu aplicación es solo para tu equipo, créala desde la ficha de una aplicación de tu organización, que solo autorizan sus personas, o guarda en tu servidor la lista de `sub` que admites. Lo explica [Quién puede autorizar tu aplicación](https://os.utilia.ai/docs/developers/oauth/apps-panel#quién-puede-autorizar-tu-aplicación).

## Verificar el `id_token`

Está firmado con RS256. Valídalo en este orden y descártalo en cuanto falle una comprobación:

**Validar un id_token**

1. **Firma** (/api/oauth/jwks): Con la clave cuyo kid coincide con el de la cabecera del token. Si no: descártalo.
2. **Emisor y destinatario** (iss y aud): iss es <https://os.utilia.ai> y aud es tu client_id. Si no: descártalo.
3. **Caducidad** (exp): Una hora después de la emisión. Si no: descártalo.
4. **Nonce** (nonce · Opcional): Si lo enviaste en la autorización, tiene que coincidir. Si no: descártalo.

Las bibliotecas OpenID Connect (Auth.js, Authlib, oidc-client-ts) hacen todo esto solas a partir de `https://os.utilia.ai/.well-known/openid-configuration`.

## Endpoint `userinfo`

**`GET /api/oauth/userinfo`**

Datos de la persona dueña del access token

Acepta `GET` y `POST`, con el access token en la cabecera `Authorization: Bearer`. El token tiene que incluir `openid`; si no, o si ha caducado o está revocado, responde `401`. Devuelve los mismos datos que el `id_token`, sin los claims técnicos (`iss`, `aud`, `exp`, `iat`, `at_hash`, `nonce`):

```json
{
  "sub": "3b1f6c2e-8a4d-4f0b-9c7e-2d5a1e6f8b90",
  "org_id": "cm9x2k4b70001qz8l5f3h6t2v",
  "name": "María García",
  "given_name": "María",
  "family_name": "García",
  "email": "maria@ejemplo.example",
  "email_verified": true
}
```

## Endpoints de descubrimiento

| Dirección | Para qué |
| - | - |
| `https://os.utilia.ai/.well-known/openid-configuration` | Documento con todas las direcciones y capacidades |
| `https://os.utilia.ai/api/oauth/jwks` | Claves públicas para verificar la firma (también en `/.well-known/jwks.json`) |
| `https://os.utilia.ai/api/oauth/userinfo` | Datos de la persona |
| `https://os.utilia.ai/api/oauth/logout` | Cierre de sesión iniciado por tu aplicación |

El detalle de cada documento está en [Descubrimiento del servidor](https://os.utilia.ai/docs/developers/oauth/discovery). Siguiente paso: [Iniciar sesión con UTILIA en Next.js](https://os.utilia.ai/docs/developers/oauth/sign-in-nextjs) o [en Flask](https://os.utilia.ai/docs/developers/oauth/sign-in-flask).
