
# Crear o reemplazar la configuración OIDC de una conexión

`PUT /v1/connections/{connection_id}/oidc` · permiso `connections:write`

Crea o reemplaza por completo la configuración de la parte que confía (relying party) de una conexión OIDC empresarial.

- **Primera escritura:** no envíe el encabezado `If-Match` e indique `sp_host` (un host activo de este entorno). La URL de callback `https://<sp_host>/sso/<connection_id>/callback` queda fija a partir de ese momento. La respuesta es `201 Created`. Si envía `If-Match` mientras no exista ninguna configuración, la solicitud falla con `412`.
- **Actualización:** `If-Match` es obligatorio. Sin él, la solicitud falla con `428` y no se escribe nada, así que una solicitud sin el encabezado nunca puede sobrescribir una configuración existente. Si la configuración cambió desde su última lectura (o el valor no coincide), la solicitud falla con `412`. `*` coincide con la configuración actual. `sp_host` debe estar vacío o ser el host almacenado.

El secreto de cliente es de solo escritura: nunca se devuelve, se registra ni se escribe en el registro de auditoría. Al actualizar sin `client_secret`, se conserva el secreto almacenado; `private_key_jwt` lo elimina (la clave de firma del entorno firma la aserción del cliente). Un `issuer` modificado borra el documento de descubrimiento almacenado; un `issuer` o `client_id` modificados invalidan la última prueba de conexión superada. Enviar la misma configuración no escribe nada. Las escrituras se auditan como `connection.protocol_config_created` o `connection.oidc_config_updated` (solo los campos modificados; el secreto aparece únicamente como `client_secret_set`).

En una conexión **activa**, una actualización que cambie `issuer` o `client_id` no se aplica. La solicitud completa (incluido un secreto nuevo) pasa a ser una [revisión en borrador](../connections/revisions.md): la respuesta es `200 OK` con la configuración activa sin cambios, su `ETag` sin cambios y un campo `revision`. Pruebe el borrador con [Probar una conexión](../connections/test.md) y `revision_id`, y después [publíquelo](../connections/publish-revision.md). Las demás actualizaciones se aplican directamente.

## Solicitud

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `connection_id` | path | sí | ID de la conexión OIDC empresarial (`conn_…`). |
| `If-Match` | header | en actualización | El `ETag` de la última lectura o escritura. Si falta en una actualización: `428`; si está obsoleto: `412`. No se envía en la primera escritura. |
| `sp_host` | string | en la primera escritura | Host activo de este entorno (en minúsculas). En una actualización, vacío o el host almacenado. |
| `issuer` | string | sí | URL `https` absoluta sin query ni fragment, de como máximo 512 bytes. |
| `client_id` | string | sí | De 1 a 255 bytes, sin caracteres de control. |
| `client_auth_method` | string | sí | `private_key_jwt` (recomendado; no se almacena ningún secreto), `client_secret_basic` o `client_secret_post`. |
| `client_secret` | string | no | Solo escritura, de como máximo 1024 bytes. Se conserva si se omite en una actualización. No permitido con `private_key_jwt`. |
| `client_secret_expires_at` | string \| null | no | Cuándo caduca el secreto en el proveedor de identidad (RFC 3339). Solo junto con `client_secret`. |
| `provider_profile` | string | no | `entra`, `okta`, `google`, `keycloak`, `ping`, `onelogin`, `jumpcloud` o `generic` (predeterminado). |
| `entra_tenant_id` | string | para `entra` | El ID de inquilino de Microsoft Entra (UUID); solo para el perfil `entra`. |

## Respuesta

`201 Created` en la primera escritura, `200 OK` en una actualización, con la configuración (véase [Obtener la configuración OIDC](get.md)) y el nuevo `ETag`. El secreto de cliente no aparece en la respuesta. Cuando la actualización pasó a ser una revisión en borrador, el cuerpo también incluye `revision` (el borrador), y la configuración y el `ETag` son los activos.

## Errores

| HTTP | código | Cuándo |
|---|---|---|
| 404 | `not_found` | La conexión no existe en este entorno o no es una conexión empresarial. |
| 409 | `conflict` | La conexión no es una conexión OIDC, `sp_host` difiere del host almacenado, o un método de secreto no tiene un secreto almacenado ni proporcionado. |
| 412 | `precondition_failed` | `If-Match` no coincide con la versión actual, la configuración cambió de forma concurrente, o se envió `If-Match` antes de la primera escritura. |
| 422 | `validation_failed` | Un campo incumple las reglas anteriores; `errors[]` señala el campo (`/issuer`, `/client_id`, `/client_auth_method`, `/client_secret`, `/client_secret_expires_at`, `/provider_profile`, `/entra_tenant_id`, `/sp_host`). |
| 428 | `precondition_required` | La configuración existe y falta el encabezado `If-Match`. |
| 400 | `unknown_field` | El cuerpo contiene un campo desconocido. |
| 401 | `unauthenticated` | La credencial falta, no es válida o ha sido revocada. |
| 403 | `insufficient_permissions` | La credencial no tiene el permiso requerido. |
| 429 | `rate_limited` | Se superó el límite de frecuencia; reintente después del periodo `Retry-After`. |
| 503 | `service_unavailable` | Una dependencia no está disponible temporalmente; reintente después del periodo `Retry-After`. |

## Ejemplo

Guarde el secreto en un archivo, no en la línea de comandos (`oidc.json`):

```json
{"sp_host":"acme.eu1.keenauth.app","issuer":"https://idp.example.com","client_id":"keenauth-acme","client_auth_method":"client_secret_basic","client_secret":"<secret from the identity provider>"}
```

```bash
curl -X PUT "https://api.eu.keenauth.com/v1/connections/conn_01j9xp6q0r1s5t7v9w1x3y5z7b/oidc" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d @oidc.json
```

```bash
keenauth connections oidc put conn_01j9xp6q0r1s5t7v9w1x3y5z7b --data @oidc.json
keenauth connections oidc put conn_01j9xp6q0r1s5t7v9w1x3y5z7b --data @oidc-update.json --if-match '"6f1c…"'
```
