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

# OAuth 연동하기

> PKCE 생성부터 사용자 동의, 토큰 교환, API 호출까지 실제 코드로 안내해 드릴게요.

사용자 동의를 받아 API를 호출하기까지의 전 과정입니다. 시작하기 전에 [개발자 콘솔](https://developers.rocketpunch.com/apps/new)에서 **OAuth 클라이언트 앱**을 등록하고 App Key · 시크릿 키 · 리다이렉트 URI를 준비해 주세요.

App Key와 시크릿 키가 OAuth 표준 용어의 `client_id` · `client_secret`에 해당합니다.

<Steps>
  <Step title="PKCE 값 만들기">
    요청마다 새로 생성해야 합니다. `S256`만 허용되며 `plain`은 거부됩니다.

    ```javascript theme={"dark"}
    import crypto from "node:crypto";

    const base64url = (buf) =>
      buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");

    const verifier  = base64url(crypto.randomBytes(32));
    const challenge = base64url(crypto.createHash("sha256").update(verifier).digest());
    ```

    ```python theme={"dark"}
    import base64, hashlib, secrets

    verifier  = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode()
    challenge = base64.urlsafe_b64encode(
        hashlib.sha256(verifier.encode()).digest()
    ).rstrip(b"=").decode()
    ```

    `verifier`는 세션이나 `state`와 묶어 보관했다가 3단계에서 함께 보냅니다. 43\~128자, 허용 문자를 벗어나면 토큰 교환이 `invalid_grant`로 실패합니다.
  </Step>

  <Step title="사용자를 동의 화면으로 보내기">
    사용자 브라우저를 아래 주소로 이동시킵니다.

    ```
    https://developers.rocketpunch.com/oauth/authorize
      ?response_type=code
      &client_id=rp_app_YOUR_KEY
      &redirect_uri=https://builder.example.com/callback
      &scope=profile%20email
      &state=RANDOM_STATE
      &code_challenge=CHALLENGE
      &code_challenge_method=S256
    ```

    가독성을 위해 줄을 나눴습니다. 실제로는 공백 없이 한 줄로 이어 붙여 보냅니다.

    사용자가 로그인하고 동의하면 등록한 리다이렉트 URI로 돌아옵니다.

    ```
    https://builder.example.com/callback?code=ONE_TIME_CODE&state=RANDOM_STATE
    ```

    <Warning>
      콜백에서 `state`가 보낸 값과 같은지 반드시 확인하세요. 확인하지 않으면 CSRF에 취약해집니다. 그리고 `code`뿐 아니라 `error` 파라미터도 함께 확인해야 합니다 — 사용자가 거부하면 `error`가 담겨 옵니다.
    </Warning>
  </Step>

  <Step title="코드를 토큰으로 바꾸기">
    빌더 서버에서 호출합니다. 브라우저에서 호출하면 시크릿 키가 노출됩니다.

    ```bash theme={"dark"}
    curl -X POST 'https://openapi.rocketpunch.com/oauth/token' \
      -u 'rp_app_YOUR_KEY:rp_sec_YOUR_SECRET' \
      -d 'grant_type=authorization_code' \
      -d 'code=ONE_TIME_CODE' \
      -d 'redirect_uri=https://builder.example.com/callback' \
      -d 'code_verifier=VERIFIER'
    ```

    ```json theme={"dark"}
    {
      "access_token": "eyJhbGciOi...",
      "token_type": "Bearer",
      "expires_in": 900,
      "refresh_token": "rt_...",
      "scope": "profile email"
    }
    ```

    `redirect_uri`는 2단계에서 보낸 값과 **글자 단위로 같아야** 합니다. `code`는 5분 안에, 한 번만 쓸 수 있습니다.
  </Step>

  <Step title="토큰으로 API 호출하기">
    ```bash theme={"dark"}
    curl 'https://openapi.rocketpunch.com/oauth/userinfo' \
      -H 'Authorization: Bearer eyJhbGciOi...'
    ```

    사용자 컨텍스트가 필요한 `/api/v1/**` 호출도 같은 방식입니다.

    ```bash theme={"dark"}
    curl -X POST 'https://openapi.rocketpunch.com/api/v1/posts' \
      -H 'Authorization: Bearer eyJhbGciOi...' \
      -H 'Content-Type: application/json' \
      -d '{"text":"로켓펀치 Open API로 올린 첫 글입니다."}'
    ```
  </Step>
</Steps>

## 토큰 갱신하기

Access Token은 15분이면 만료됩니다.

```bash theme={"dark"}
curl -X POST 'https://openapi.rocketpunch.com/oauth/token' \
  -u 'rp_app_YOUR_KEY:rp_sec_YOUR_SECRET' \
  -d 'grant_type=refresh_token' \
  -d 'refresh_token=rt_...'
```

<Warning>
  응답에 새 `refresh_token`이 함께 옵니다. **반드시 저장하고 이전 값은 버리세요.** Refresh Token은 쓸 때마다 교체되므로 이전 값으로 다시 요청하면 실패합니다.
</Warning>

## 연동을 끊을 때

사용자가 연결 해제를 요청하면 토큰을 폐기합니다.

```bash theme={"dark"}
curl -X POST 'https://openapi.rocketpunch.com/oauth/revoke' \
  -u 'rp_app_YOUR_KEY:rp_sec_YOUR_SECRET' \
  -d 'token=rt_...'
```

## 자주 만나는 오류

| 상황                            | 원인                                                | 해결                                           |
| ----------------------------- | ------------------------------------------------- | -------------------------------------------- |
| 동의 화면 대신 400 페이지가 뜹니다         | `client_id`가 등록되지 않았거나 `redirect_uri`가 등록값과 다릅니다  | 콘솔의 등록값과 글자 단위로 비교하세요. 끝 슬래시도 포함됩니다          |
| 토큰 교환이 `invalid_grant`로 실패합니다 | `code`를 이미 썼거나 5분이 지났거나, `code_verifier`가 맞지 않습니다 | 코드는 1회용입니다. `verifier`를 세션에서 제대로 꺼내오는지 확인하세요 |
| `redirect_uri` 불일치 오류         | 2단계와 3단계의 값이 다릅니다                                 | 두 요청에 완전히 같은 문자열을 넣으세요                       |
| `403` · `C0011`               | 토큰에 필요한 권한이 없습니다                                  | 필요한 scope를 포함해 다시 동의를 받으세요                   |

<Note>
  안전을 위해 리다이렉트 URI 검증에 실패하면 사용자에게 400 페이지를 보여 주고, 등록되지 않은 주소로는 절대 리다이렉트하지 않습니다.
</Note>

## 운영 시 확인할 것

* 시크릿 키를 브라우저나 모바일 앱 번들에 넣지 마세요. 토큰 교환은 반드시 서버에서 합니다.
* `state`는 요청마다 새로 만들고 콜백에서 검증하세요.
* Refresh Token은 사용자별로 안전하게 저장하고, 갱신할 때마다 새 값으로 덮어쓰세요.
* 사용자가 연결을 해제하면 저장된 토큰을 폐기하고 삭제하세요.
