
# OAuth 2.0 및 OpenID Connect 엔드포인트

> **상태: `planned`.** 이 페이지는 계획서(§13)에 명시된 모든 KeenAuth 환경의 프로토콜 표면을 설명합니다. 엔드포인트는 2단계에서 구현 중이며 아직 호스팅 환경에서 사용할 수 없습니다. 일반 제공 전까지 세부 사항은 변경될 수 있습니다.

모든 환경은 고유한 OAuth 2.0 인가 서버이자 OpenID Connect 공급자입니다. 엔드포인트는 관리 API의 일부가 아니며, 환경 호스트인 `https://<environment-slug>.<region>.keenauth.app` 또는 검증된 사용자 지정 도메인에 있습니다. 표에서 `<issuer>`는 환경의 issuer이며, 디스커버리 문서의 `issuer` 값입니다.

## 엔드포인트

| 엔드포인트 | 메서드 및 경로 | 클라이언트 인증 |
|---|---|---|
| 디스커버리 | `GET <issuer>/.well-known/openid-configuration` 및 `GET <issuer>/.well-known/oauth-authorization-server` (RFC 8414, 내용 동일) | 없음 |
| 키 | `GET <issuer>/.well-known/jwks.json` | 없음 |
| 인가 | `GET` 또는 `POST <login host>/oauth2/authorize` | 없음 (브라우저) |
| 푸시된 인가 요청 (RFC 9126) | `POST <issuer>/oauth2/par` | 기밀 클라이언트는 필수, 공개 클라이언트는 `client_id` 필요 |
| 토큰 | `POST <issuer>/oauth2/token` | 기밀 클라이언트는 필수, 공개 클라이언트는 `client_id` 필요 (DPoP는 선택 사항) |
| 기기 인가 (RFC 8628) | `POST <issuer>/oauth2/device_authorization` | 토큰 엔드포인트와 동일 |
| 해지 (RFC 7009) | `POST <issuer>/oauth2/revoke` | 토큰 엔드포인트와 동일 |
| 내성 (RFC 7662) | `POST <issuer>/oauth2/introspect` | 기밀 클라이언트만 가능 |
| UserInfo | `GET` 또는 `POST <issuer>/oauth2/userinfo` | Bearer 또는 DPoP 액세스 토큰 |
| 세션 종료 | `GET` 또는 `POST <login host>/oauth2/logout` | 없음 (`id_token_hint` 또는 `client_id`) |

`<login host>`는 환경의 모든 로그인 도메인을 뜻하며, 기본 호스트 또는 추가된 사용자 지정 도메인이 해당합니다. 디스커버리 문서는 issuer 호스트에서만 제공됩니다. 다른 로그인 호스트에서 `/.well-known/openid-configuration`을 요청하면 본문에 issuer URL을 담은 `404`가 반환됩니다.

## 그랜트 유형

| 그랜트 | 용도 |
|---|---|
| `authorization_code` | 브라우저 및 네이티브 로그인. PKCE `S256`이 필수입니다. |
| `refresh_token` | 토큰을 갱신합니다. 사용할 때마다 리프레시 토큰이 교체되며, 이전 토큰을 재사용하면 토큰 패밀리 전체가 해지됩니다. |
| `client_credentials` | 기기 간(machine-to-machine) 액세스. |
| `urn:ietf:params:oauth:grant-type:device_code` | TV나 명령줄 도구처럼 키보드가 없는 기기. |
| `urn:ietf:params:oauth:grant-type:token-exchange` | 서비스 간 위임 및 가장(impersonation). |

암묵적 흐름(`response_type=token` 및 `id_token`을 포함하는 모든 값), 리소스 소유자 비밀번호 자격 증명 그랜트, 하이브리드 응답 유형은 현재와 향후 모두 지원되지 않습니다. RFC 9700은 앞의 두 가지를 금지합니다.

## 기기 인가

브라우저가 없는 기기는 `POST <issuer>/oauth2/device_authorization`을 호출해 흐름을 시작합니다. 기기 그랜트를 명시적으로 등록한 `device` 또는 `native` 유형의 애플리케이션만 이 흐름을 사용할 수 있습니다. 응답에는 `device_code`, `user_code`, 인증 주소가 포함됩니다. 사용자는 다른 기기에서 해당 주소를 열고 로그인한 뒤 승인합니다. 그동안 기기는 `grant_type=urn:ietf:params:oauth:grant-type:device_code`를 사용해 토큰 엔드포인트를 폴링합니다. `user_code`는 8자리이며 `XXXX-XXXX` 형식으로 표시됩니다. 기기 코드는 10분 동안 유효하며 한 번만 사용할 수 있습니다. 기기는 최대 5초마다 폴링해야 하며, 더 빠르게 폴링하면 `slow_down`이 반환되고 간격에 5초가 추가됩니다. `verification_uri`는 `https://<login host>/u/device`이며, 사용자는 승인하기 전에 애플리케이션, 요청된 스코프, 요청을 시작한 기기의 국가를 확인합니다.

## 보안 규칙

환경은 예외 없이 OAuth 2.0 보안 모범 사례(RFC 9700)를 따릅니다.

- 리디렉션 URI는 등록된 URI와 바이트 단위로 정확히 일치해야 합니다. 와일드카드는 사용할 수 없습니다.
- PKCE는 `S256`만 허용하며 `plain`은 항상 거부됩니다. PKCE는 공개 클라이언트에 필수이며 모든 애플리케이션에서 기본으로 켜져 있습니다. 기밀 `web` 애플리케이션은 설정에서 이를 끌 수 있지만, 이 경우 환경이 여전히 `nonce`를 요구해야 합니다.
- 공식 SDK는 128비트 `state`와 `nonce`를 보내고, 반환 시 두 값을 모두 검사합니다.
- 인가 코드는 60초 동안 유효하며 한 번만 사용할 수 있습니다. 이미 사용된 코드를 제시하면 `invalid_grant`가 반환되고, 첫 번째 사용으로 발급된 리프레시 토큰과 액세스 토큰이 해지됩니다.
- 푸시된 인가 요청을 사용하면 요청 파라미터가 브라우저 주소 표시줄에 노출되지 않습니다. 모든 요금제에서 사용할 수 있으며, `require_par` 설정을 켜면 애플리케이션 또는 환경 전체에 이를 필수로 적용할 수 있습니다.
- 리프레시 토큰은 사용할 때마다 교체되며, 재사용 감지 시 해당 패밀리가 해지됩니다.
- DPoP(RFC 9449)는 액세스 토큰을 클라이언트가 보관하는 키에 바인딩합니다. API에서 바인딩된 토큰을 검증하는 방법은 [API에서 토큰 검증하기](/docs/sdk/token-verification)를 참고하십시오.

## 세션과 로그아웃

로그인에 성공하면 환경은 로그인 호스트의 `HttpOnly` 쿠키에 단일 Single Sign-On 세션 하나를 보관합니다. 같은 환경의 애플리케이션은 이 세션을 재사용하므로, 정책상 더 강력하거나 최신인 인증이 필요하지 않다면 두 번째 애플리케이션에서 비밀번호를 다시 묻지 않습니다. 코드는 쿠키를 직접 보지 않으며, 인가 응답만 받습니다.

세션 종료 엔드포인트(OpenID Connect RP-Initiated Logout 1.0)는 해당 세션을 종료하고, 세션에 속한 리프레시 토큰을 해지하며, 백채널 로그아웃 주소를 등록한 애플리케이션에 알립니다. 백엔드에서 사용자의 세션을 조회하거나 해지하려면 [관리 API의 세션 엔드포인트](/docs/api/management/users/list-sessions)를 사용하십시오.

사용자가 엔터프라이즈 연결로 로그인했다면, 세션 종료 요청에 `federated=true`를 추가하여 ID 공급자에서도 로그아웃시킬 수 있습니다. 이 경우 먼저 여기의 세션이 종료되고, 브라우저는 ID 공급자의 로그아웃(SAML 단일 로그아웃 또는 OpenID 공급자의 세션 종료 엔드포인트)으로 이동한 뒤 `post_logout_redirect_uri`로 돌아옵니다. 이는 연결에서 ID 공급자 로그아웃이 켜져 있고 공급자가 로그아웃 주소를 공개한 경우에만 수행됩니다. 그렇지 않거나 공급자에 연결할 수 없으면, 이 파라미터가 없을 때와 같이 요청이 처리됩니다. ID 공급자에서 로그아웃하면 해당 공급자를 사용하는 사용자의 다른 애플리케이션도 종료될 수 있습니다.
