---
title: 인가 요청
description: GET https://paysync.kr/oauth/authorize 로 고객을 페이싱크 연결 화면에 보내는 인가 요청의 쿼리 파라미터, PKCE, 리디렉션으로 돌아오는 인가 코드와 오류를 설명해요.
sidebar:
  order: 1
  badge: GET
---

```http
GET https://paysync.kr/oauth/authorize
```

고객의 브라우저를 페이싱크 연결 화면으로 보내요. API 서버(`api.paysync.kr`)가 아니라 대시보드 주소(`paysync.kr`)예요. 고객이 로그인하고 워크스페이스를 고른 뒤 허용하면 `redirect_uri`로 인가 코드가 돌아와요. 고객이 보는 화면은 [연결된 앱](/connected-apps#앱-연결하기)을 참고하세요.

## 쿼리 파라미터

| 파라미터 | 타입 | 필수 | 설명 |
| --- | --- | --- | --- |
| `response_type` | string | 예 | `code`로 보내요. |
| `client_id` | string | 예 | 앱 ID(`oap_...`)예요. |
| `redirect_uri` | string | 예 | 인가 코드를 받을 주소예요. 앱에 등록한 값과 똑같아야 해요. |
| `scope` | string | 예 | 요청할 [스코프](/partners/scopes)를 공백으로 구분해 넣어요. 예: `invoices:read transactions:read`. 앱에 등록된 스코프만 요청할 수 있어요. |
| `state` | string | | 인가 코드와 함께 그대로 돌려줘요. 요청마다 추측할 수 없는 값을 만들어 세션에 저장하고, 돌아왔을 때 같은지 확인하세요. |
| `code_challenge` | string | | PKCE를 쓸 때 넣어요. `code_verifier`를 SHA-256으로 해시하고 패딩 없는 base64url로 인코딩한 43자예요. [PKCE](#pkce) 참고 |
| `code_challenge_method` | string | | `S256`만 지원해요. `code_challenge`를 보낼 때 꼭 넣어요. |

쿼리 값은 URL 인코딩해요. `scope`의 공백은 `%20`이나 `+`로 보내요.

## 요청 예시

```http URL
https://paysync.kr/oauth/authorize?response_type=code&client_id=oap_a1b2c3d4e5f6g7h8i9j0k1l2&redirect_uri=https%3A%2F%2Fpartner.example.com%2Fpaysync%2Fcallback&scope=invoices%3Aread%20transactions%3Aread&state=Zx81kQ0aLp&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256
```

```js Node.js
import { createHash, randomBytes } from "node:crypto";

const state = randomBytes(16).toString("base64url");
const codeVerifier = randomBytes(32).toString("base64url");
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
// state와 codeVerifier는 고객 세션에 저장해 두세요.

const url = new URL("https://paysync.kr/oauth/authorize");
url.search = new URLSearchParams({
  response_type: "code",
  client_id: process.env.PAYSYNC_CLIENT_ID,
  redirect_uri: "https://partner.example.com/paysync/callback",
  scope: "invoices:read transactions:read",
  state,
  code_challenge: codeChallenge,
  code_challenge_method: "S256",
}).toString();

res.redirect(url.toString());
```

```python Python
import base64
import hashlib
import os
import secrets
from urllib.parse import urlencode

state = secrets.token_urlsafe(16)
code_verifier = secrets.token_urlsafe(32)
code_challenge = (
    base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest())
    .rstrip(b"=")
    .decode()
)
# state와 code_verifier는 고객 세션에 저장해 두세요.

url = "https://paysync.kr/oauth/authorize?" + urlencode(
    {
        "response_type": "code",
        "client_id": os.environ["PAYSYNC_CLIENT_ID"],
        "redirect_uri": "https://partner.example.com/paysync/callback",
        "scope": "invoices:read transactions:read",
        "state": state,
        "code_challenge": code_challenge,
        "code_challenge_method": "S256",
    }
)
# 고객의 브라우저를 url로 리디렉션해요.
```

## 응답

연결 화면은 JSON을 돌려주지 않아요. 고객의 브라우저를 `redirect_uri`로 이동시켜요. `redirect_uri`에 원래 쿼리가 있으면 그대로 두고 뒤에 붙여요.

### 고객이 허용했을 때

`code`를 붙여 이동해요. `state`를 보냈다면 `state`도 붙여요.

```http 허용
https://partner.example.com/paysync/callback?code=k3jq8qxvb7rt2mlw9syda1b2c3d4e5f6g7h8i9j0&state=Zx81kQ0aLp
```

| 파라미터 | 설명 |
| --- | --- |
| `code` | 인가 코드예요. 고객이 고른 워크스페이스와 허용한 스코프가 담겨 있어요. |
| `state` | 인가 요청에 보낸 `state`예요. |

인가 코드는 10분 동안 한 번만 쓸 수 있어요. 받은 즉시 [토큰 발급](/partners/oauth/token)으로 바꾸세요.

### 고객이 취소했을 때

연결 화면에서 **취소**를 누르면 `error=access_denied`를 붙여 이동해요. `state`를 보냈다면 `state`도 붙여요.

```http 취소
https://partner.example.com/paysync/callback?error=access_denied&state=Zx81kQ0aLp
```

## 오류

아래 경우에는 `redirect_uri`로 돌아가지 않고 연결 화면에 오류를 보여 줘요. 잘못된 주소로 코드가 새지 않게 하려는 동작이에요.

| 연결 화면의 안내 | 발생 조건 |
| --- | --- |
| 잘못된 연결 요청이에요 | `response_type`이 `code`가 아니거나, `client_id`나 `redirect_uri`가 없거나, `scope`가 비었거나 모르는 값이 있어요. |
| 등록되지 않은 앱이에요 | `client_id`에 맞는 앱이 없어요. |
| 앱에 등록되지 않은 돌아갈 주소예요 | `redirect_uri`가 앱에 등록되지 않았어요. |
| 앱에 허용되지 않은 권한을 요청했어요 | 앱에 등록되지 않은 스코프를 요청했어요. |
| 앱의 연결 요청이 올바르지 않아요 | `code_challenge`가 43자 base64url이 아니거나 `code_challenge_method`가 `S256`이 아니에요. 고객이 **연결 허용**을 누를 때 떠요. |

## PKCE

PKCE를 쓰면 인가 코드가 중간에 새도 `code_verifier` 없이는 토큰으로 바꿀 수 없어요. 클라이언트 시크릿을 보관하는 서버에서도 함께 쓰는 것을 권장해요.

1. 요청마다 `A-Z`, `a-z`, `0-9`, `-`, `.`, `_`, `~`로 된 43~128자 `code_verifier`를 만들어 세션에 저장해요.
2. `code_challenge`는 `BASE64URL(SHA256(code_verifier))`이고, 끝의 `=`는 빼요.
3. 인가 요청에 `code_challenge`와 `code_challenge_method=S256`을 넣어요.
4. [토큰 요청](/partners/oauth/token)에 같은 `code_verifier`를 넣어요.

32바이트 난수를 base64url로 인코딩하면 43자 `code_verifier`가 돼요. 위 [요청 예시](#요청-예시)의 Node.js와 Python 코드가 이 방식이에요.
