
# 연결의 OIDC 설정 생성 또는 교체

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

엔터프라이즈 OIDC 연결의 Relying Party 설정을 생성하거나 전체를 교체합니다.

- **최초 쓰기:** `If-Match` 헤더를 보내지 말고 `sp_host`(이 환경의 활성 호스트)를 지정하십시오. 이후 콜백 `https://<sp_host>/sso/<connection_id>/callback`이 고정됩니다. 응답은 `201 Created`입니다. 설정이 없는 상태에서 `If-Match`를 보내면 `412`로 실패합니다.
- **업데이트:** `If-Match`가 필요합니다. 없으면 요청은 `428`로 실패하며 아무것도 기록되지 않으므로, 헤더 없이 기존 설정을 덮어쓸 수 없습니다. 마지막으로 조회한 이후 설정이 변경되었거나 값이 일치하지 않으면 `412`로 실패합니다. `*`는 현재 설정과 일치합니다. `sp_host`는 비어 있거나 저장된 호스트여야 합니다.

클라이언트 비밀 값은 쓰기 전용입니다. 반환, 로그 기록, 감사 로그 기록이 되지 않습니다. `client_secret` 없이 업데이트하면 저장된 비밀 값이 유지됩니다. `private_key_jwt`를 사용하면 비밀 값이 제거되며, 이때 환경의 서명 키가 클라이언트 어설션에 서명합니다. `issuer`를 변경하면 저장된 디스커버리 문서가 삭제되고, `issuer` 또는 `client_id`를 변경하면 마지막으로 통과한 연결 테스트가 무효화됩니다. 동일한 설정을 보내면 아무것도 기록되지 않습니다. 쓰기 작업은 `connection.protocol_config_created` 또는 `connection.oidc_config_updated`로 감사 기록됩니다(변경된 필드만 기록되며, 비밀 값은 `client_secret_set`으로만 표시됩니다).

**활성** 연결에서 `issuer` 또는 `client_id`를 변경하는 업데이트는 적용되지 않습니다. 새 비밀 값을 포함한 요청 전체가 [초안 리비전](../connections/revisions.md)이 됩니다. 응답은 변경되지 않은 실시간 설정, 변경되지 않은 `ETag`, `revision` 필드와 함께 `200 OK`입니다. `revision_id`로 [연결 테스트](../connections/test.md)를 수행한 뒤 [게시](../connections/publish-revision.md)하십시오. 그 밖의 업데이트는 바로 적용됩니다.

## 요청

| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
| `connection_id` | path | yes | 엔터프라이즈 OIDC 연결 ID(`conn_…`). |
| `If-Match` | header | 업데이트 시 | 마지막 조회 또는 쓰기의 `ETag`. 업데이트 시 없으면 `428`, 오래된 값이면 `412`. 최초 쓰기에는 보내지 않습니다. |
| `sp_host` | string | 최초 쓰기 시 | 이 환경의 활성 호스트(소문자). 업데이트 시 비어 있거나 저장된 호스트여야 합니다. |
| `issuer` | string | yes | 쿼리 또는 프래그먼트가 없는 절대 `https` URL, 최대 512바이트. |
| `client_id` | string | yes | 1–255바이트, 제어 문자 불가. |
| `client_auth_method` | string | yes | `private_key_jwt`(권장, 비밀 값을 저장하지 않음), `client_secret_basic` 또는 `client_secret_post`. |
| `client_secret` | string | no | 쓰기 전용, 최대 1024바이트. 업데이트 시 생략하면 유지됩니다. `private_key_jwt`와 함께 사용할 수 없습니다. |
| `client_secret_expires_at` | string \| null | no | ID 공급자가 비밀 값을 만료하는 시점(RFC 3339). `client_secret`과 함께만 사용합니다. |
| `provider_profile` | string | no | `entra`, `okta`, `google`, `keycloak`, `ping`, `onelogin`, `jumpcloud` 또는 `generic`(기본값). |
| `entra_tenant_id` | string | `entra`의 경우 | Microsoft Entra 테넌트 ID(UUID). `entra` 프로필에서만 사용합니다. |

## 응답

최초 쓰기는 `201 Created`, 업데이트는 `200 OK`와 함께 설정([OIDC 설정 조회](get.md) 참조)과 새 `ETag`를 반환합니다. 클라이언트 비밀 값은 응답에 포함되지 않습니다. 업데이트가 초안 리비전이 된 경우 본문에는 `revision`(초안)이 포함되며, 설정과 `ETag`는 실시간 값입니다.

## 오류

| HTTP | code | 발생 시점 |
|---|---|---|
| 404 | `not_found` | 이 환경에 연결이 없거나 엔터프라이즈 연결이 아닙니다. |
| 409 | `conflict` | 연결이 OIDC 연결이 아니거나, `sp_host`가 저장된 호스트와 다르거나, 비밀 값 방식에 저장되었거나 제공된 비밀 값이 없습니다. |
| 412 | `precondition_failed` | `If-Match`가 현재 버전과 일치하지 않거나, 설정이 동시에 변경되었거나, 최초 쓰기 전에 `If-Match`를 보냈습니다. |
| 422 | `validation_failed` | 필드가 위의 규칙을 위반합니다. `errors[]`가 해당 필드를 가리킵니다(`/issuer`, `/client_id`, `/client_auth_method`, `/client_secret`, `/client_secret_expires_at`, `/provider_profile`, `/entra_tenant_id`, `/sp_host`). |
| 428 | `precondition_required` | 설정이 존재하는데 `If-Match` 헤더가 없습니다. |
| 400 | `unknown_field` | 본문에 알 수 없는 필드가 있습니다. |
| 401 | `unauthenticated` | 자격 증명이 없거나, 잘못되었거나, 취소되었습니다. |
| 403 | `insufficient_permissions` | 자격 증명에 필요한 권한이 없습니다. |
| 429 | `rate_limited` | 호출 한도를 초과했습니다. `Retry-After` 기간 이후에 다시 시도하십시오. |
| 503 | `service_unavailable` | 종속 구성 요소를 일시적으로 사용할 수 없습니다. `Retry-After` 기간 이후에 다시 시도하십시오. |

## 예제

비밀 값은 명령줄이 아닌 파일에 보관하십시오(`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…"'
```
