> ## Documentation Index
> Fetch the complete documentation index at: https://docs.golfjoin.co.kr/llms.txt
> Use this file to discover all available pages before exploring further.

# 인증

> OAuth 2.0 인가 코드 + PKCE로 매니저 토큰을 받고 갱신합니다.

> 매니저가 브라우저에서 조인성공에 직접 로그인하고, 프로그램은 토큰만 받습니다. 프로그램은 매니저의 비밀번호를 보지 않습니다.

클라이언트 시크릿 없이 PKCE로 보호하는 방식(public client)입니다. 설치형 프로그램은 시크릿을 안전하게 숨길 수 없기 때문에, 웹 서비스도 같은 방식으로 맞췄습니다. [RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252)(네이티브 앱 OAuth)의 루프백 방식을 따릅니다.

## 클라이언트 정보

| 항목 | 값 |
| - | - |
| `client_id` | 연동을 신청하면 조인성공이 발급합니다. 신청은 [API 개요의 문의](https://docs.golfjoin.co.kr/api-introduction/overview.md#문의)를 참고하세요. |
| 클라이언트 시크릿 | 없음 |
| PKCE | `S256` 필수 |
| `redirect_uri` | 설치형 프로그램은 `http://127.0.0.1:{포트}/{경로}`이고 포트는 매번 달라도 됩니다. `localhost`는 받지 않습니다. 웹 서비스는 연동 신청 때 등록한 `https` 주소만 씁니다. |
| 권한 범위 | 내 티 매물 조회·등록·수정·마감, 끌어올리기, 월 사용량 조회 |
| access token 수명 | 1시간 (`expires_in: 3600`) |
| refresh token 수명 | 60일 (`refresh_expires_in: 5184000`) |

이 밖의 매니저 API(통계 등)를 호출하면 `403 forbidden`을 돌려줍니다.

## 로그인 흐름

### 1. PKCE 값 만들기

| 값 | 만드는 법 |
| - | - |
| `code_verifier` | 43\~128자의 랜덤 문자열 (base64url 문자: `A-Z a-z 0-9 - _`) |
| `code_challenge` | `BASE64URL(SHA256(code_verifier))`, 끝의 `=`는 뺍니다 |
| `state` | 요청마다 새로 만드는 랜덤 문자열 |

### 2. 브라우저로 동의 화면 열기

프로그램 안의 웹뷰가 아니라 **매니저의 기본 브라우저**로 엽니다. 이미 조인성공에 로그인해 둔 브라우저라면 로그인 단계 없이 동의 화면이 바로 나옵니다.

```text theme={null}
GET https://golfjoin.co.kr/oauth/authorize
  ?response_type=code
  &client_id={client_id}
  &redirect_uri={redirect_uri}
  &state={state}
  &code_challenge={code_challenge}
  &code_challenge_method=S256
```

| 파라미터 | 필수 | 설명 |
| - | - | - |
| `response_type` | O | `code` 고정 |
| `client_id` | O | 발급받은 클라이언트 ID |
| `redirect_uri` | O | 프로그램이 기다리는 루프백 주소. URL 인코딩해서 넣습니다. |
| `state` | O | 1단계에서 만든 값 |
| `code_challenge` | O | 1단계에서 만든 값 |
| `code_challenge_method` | O | `S256` 고정 |

동의 화면에는 연결하려는 프로그램 이름과 요청 권한이 표시됩니다. 승인된 매니저가 아니거나 플랜을 이용 중이 아니면, 허용 버튼 대신 이용할 수 없다는 안내가 나옵니다.

### 3. 돌아온 주소 받기

| 결과 | 돌아오는 주소 |
| - | - |
| 허용 | `{redirect_uri}?code={code}&state={state}` |
| 거부 | `{redirect_uri}?error=access_denied&state={state}` |

> [!WARNING]
>
> `state`가 처음 보낸 값과 다르면 다른 곳에서 만든 응답일 수 있으니 버리세요. `code`는 5분 안에, 한 번만 쓸 수 있습니다.

### 4. 토큰 받기

```bash theme={null}
curl -X POST https://golfjoin.co.kr/api/v1/oauth/token \
  -d grant_type=authorization_code \
  -d client_id={client_id} \
  -d code={code} \
  -d redirect_uri={redirect_uri} \
  -d code_verifier={code_verifier}
```

| 파라미터 | 필수 | 설명 |
| - | - | - |
| `grant_type` | O | `authorization_code` |
| `client_id` | O | 발급받은 클라이언트 ID |
| `code` | O | 3단계에서 받은 값 |
| `redirect_uri` | O | 2단계와 **글자 하나까지 같은** 주소 |
| `code_verifier` | O | 1단계에서 만든 원래 값 |

```json theme={null}
{
  "access_token": "Y9m1...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "q2Lk...",
  "refresh_expires_in": 5184000
}
```

### 5. API 호출

```bash theme={null}
curl https://golfjoin.co.kr/api/v1/manager/contents \
  -H "Authorization: Bearer {access_token}"
```

## 토큰 갱신

access token이 만료되기 전이나 API가 `403`을 돌려줬을 때 refresh token으로 새 토큰을 받습니다.

```bash theme={null}
curl -X POST https://golfjoin.co.kr/api/v1/oauth/token \
  -d grant_type=refresh_token \
  -d client_id={client_id} \
  -d refresh_token={refresh_token}
```

응답 형식은 처음 토큰을 받을 때와 같습니다. 갱신할 때마다 **access token과 refresh token이 모두 새로 발급되고, 이전 두 토큰은 그 즉시 쓸 수 없습니다.** 새 refresh token을 저장한 다음에 이전 값을 지우세요.

> [!WARNING]
>
> 이미 한 번 쓴 refresh token이 다시 들어오면 토큰이 새어 나간 것으로 보고, 그 매니저와 이 프로그램의 연결을 모두 끊습니다. 이 경우 매니저가 다시 로그인해야 합니다. 갱신 요청이 네트워크 오류로 응답 없이 끝났다면 같은 refresh token으로 바로 다시 보내지 말고, 먼저 새 토큰이 저장됐는지 확인하세요. 저장된 게 없으면 재로그인으로 처리하는 편이 안전합니다.

갱신이 `400 invalid_grant`로 실패하면 refresh token이 만료됐거나, 연결이 해제됐거나, 이미 쓴 값입니다. 다시 로그인 흐름을 시작하세요.

## 연결 해제

### 프로그램에서 해제

프로그램의 「연결 해제」나 로그아웃에서 호출합니다. refresh token을 보내면 짝이 되는 access token도 함께 무효가 됩니다.

```bash theme={null}
curl -X POST https://golfjoin.co.kr/api/v1/oauth/revoke \
  -H "Content-Type: application/json" \
  -d '{ "token": "{refresh_token}" }'
```

```json theme={null}
{ "ok": true }
```

이미 무효인 토큰을 보내도 같은 응답을 돌려줍니다.

### 조인성공에서 해제

매니저는 조인성공 콘솔 **티 관리** 화면의 「연결된 외부 프로그램」에서 프로그램별로 연결을 끊을 수 있습니다. 끊으면 그 프로그램이 그 매니저 계정으로 받은 토큰이 모두 무효가 되고, 다음 API 호출부터 `403`, 다음 갱신부터 `400 invalid_grant`를 받습니다.

## 토큰 보관

* 설치형 프로그램은 토큰을 매니저 PC에 암호화해서 저장하세요. Windows는 DPAPI, macOS는 키체인을 권합니다.
* 웹 서비스는 토큰을 서버에만 두고 암호화해서 저장하세요. 브라우저(로컬 스토리지, 쿠키)로 내려보내지 마세요.
* 로그, 오류 보고, 화면에 토큰 전체를 남기지 마세요. 확인이 필요하면 앞 몇 자리만 쓰세요.
* 토큰은 그 매니저 한 명의 권한입니다. 다른 매니저 계정의 작업에 쓰지 마세요.

## 오류

| HTTP | `error` | 언제 |
| - | - | - |
| 400 | `invalid_request` | 필수 파라미터가 빠졌습니다. |
| 400 | `unsupported_grant_type` | `grant_type`이 `authorization_code`, `refresh_token`이 아닙니다. |
| 400 | `invalid_grant` | `code`가 만료·재사용됐거나, `redirect_uri`·`code_verifier`가 맞지 않거나, refresh token이 만료·무효입니다. |
| 403 | `forbidden` | API 호출 시 토큰이 없거나 만료·무효이거나, 권한 범위 밖이거나, 매니저·플랜 상태가 바뀌었습니다. |

전체 목록은 [에러 코드](https://docs.golfjoin.co.kr/api-introduction/error-codes.md)를 참고하세요.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.