# OAuth 2.0 엔드포인트

> **gency-mcp는 OAuth 만 사용합니다.** API Key 인증 경로는 MCP 런타임에 존재하지 않으며, 모든 사용자 인증은 아래 OAuth 엔드포인트를 통해 진행됩니다.

## 엔드포인트 목록

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | /oauth/authorize | Authorization Code 발급 (PKCE) |
| GET | /oauth/authorize/info | 동의 화면용 앱·scope 메타 |
| POST | /oauth/auth | 이메일/비번 로그인 (authorize_key 세션 연결) |
| POST | /oauth/auth/google | Google 로그인 (authorize_key 세션 연결) |
| POST | /oauth/auth/select-master | EXTERNAL 앱 master(팀) 선택 |
| POST | /oauth/token | Access Token 발급/갱신 |
| POST | /oauth/revoke | 토큰 폐기 |
| POST | /oauth/introspect | 토큰 유효성 검증 |
| GET | /.well-known/oauth-authorization-server | 서버 메타데이터 |

## 앱 타입

OAuth 앱은 두 종류로 구분됩니다:

| 타입 | 설명 | master 결정 | API Key | client_credentials |
|---|---|---|---|---|
| `INTERNAL` | 단일 master 고정 (기존 방식) | 앱 생성 시 고정 | 발급 가능 | 사용 가능 |
| `EXTERNAL` | 사용자가 로그인 후 master 선택 (MCP 등 외부 통합용) | `/oauth/auth/select-master` 로 선택 | 차단 | 차단 |

EXTERNAL 앱 인가 플로우:

```
1. GET /oauth/authorize?response_type=code&client_id=...&redirect_uri=...&code_challenge=...&code_challenge_method=S256
   → authorize_key 발급, 프론트엔드 동의 화면으로 리다이렉트
2. POST /oauth/auth                  (또는 /oauth/auth/google) — 사용자 인증
3. POST /oauth/auth/select-master    — 사용자 master 목록에서 1개 선택
4. GET /oauth/authorize?authorize_key=... — authorization_code 발급, redirect_uri 로 리다이렉트
5. POST /oauth/token (PKCE code_verifier 포함) — access_token + refresh_token 발급
```

## POST /oauth/token

OAuth2 Access Token을 발급하거나 갱신합니다. `application/x-www-form-urlencoded` 형식을 사용합니다.

### 파라미터

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| grant_type | string | Y | `authorization_code`, `refresh_token` 또는 `client_credentials` |
| client_id | string | Y | 앱 Client ID |
| client_secret | string | N | 앱 Client Secret |
| code | string | N | 인가 코드 (authorization_code 시) |
| redirect_uri | string | N | 리다이렉트 URI |
| refresh_token | string | N | 리프레시 토큰 (refresh_token 시) |
| code_verifier | string | N | PKCE 검증자 |
| scope | string | N | 요청 스코프 |

### 응답 (200)

```json
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJhbGciOi...",
  "scope": "product:read product:write"
}
```

## POST /oauth/revoke

토큰을 폐기합니다.

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| token | string | Y | 폐기할 토큰 |
| client_id | string | Y | Client ID |
| client_secret | string | Y | Client Secret |

## POST /oauth/introspect

토큰의 유효성을 검증합니다.

### 응답 (200)

```json
{
  "active": true,
  "scope": "product:read product:write",
  "client_id": "app_001",
  "token_type": "Bearer",
  "exp": 1712700000,
  "iat": 1712696400,
  "sub": "user_001"
}
```

## GET /.well-known/oauth-authorization-server

OAuth2 서버 메타데이터를 반환합니다.

### 응답 (200)

```json
{
  "issuer": "https://openapi.gency.ai",
  "authorization_endpoint": "https://openapi.gency.ai/oauth/authorize",
  "token_endpoint": "https://openapi.gency.ai/oauth/token",
  "introspection_endpoint": "https://openapi.gency.ai/oauth/introspect",
  "revocation_endpoint": "https://openapi.gency.ai/oauth/revoke",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
  "scopes_supported": ["product:read", "product:write", "team:read", "team:write", "user:read"],
  "code_challenge_methods_supported": ["S256"]
}
```
