인가 요청
GET https://paysync.kr/oauth/authorize 로 고객을 페이싱크 연결 화면에 보내는 인가 요청의 쿼리 파라미터, PKCE, 리디렉션으로 돌아오는 인가 코드와 오류를 설명해요.
GET https://paysync.kr/oauth/authorize
고객의 브라우저를 페이싱크 연결 화면으로 보내요. API 서버(api.paysync.kr)가 아니라 대시보드 주소(paysync.kr)예요. 고객이 로그인하고 워크스페이스를 고른 뒤 허용하면 redirect_uri로 인가 코드가 돌아와요. 고객이 보는 화면은 연결된 앱을 참고하세요.
쿼리 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
response_type |
string | 예 | code로 보내요. |
client_id |
string | 예 | 앱 ID(oap_...)예요. |
redirect_uri |
string | 예 | 인가 코드를 받을 주소예요. 앱에 등록한 값과 똑같아야 해요. |
scope |
string | 예 | 요청할 스코프를 공백으로 구분해 넣어요. 예: invoices:read transactions:read. 앱에 등록된 스코프만 요청할 수 있어요. |
state |
string | 인가 코드와 함께 그대로 돌려줘요. 요청마다 추측할 수 없는 값을 만들어 세션에 저장하고, 돌아왔을 때 같은지 확인하세요. | |
code_challenge |
string | PKCE를 쓸 때 넣어요. code_verifier를 SHA-256으로 해시하고 패딩 없는 base64url로 인코딩한 43자예요. PKCE 참고 |
|
code_challenge_method |
string | S256만 지원해요. code_challenge를 보낼 때 꼭 넣어요. |
쿼리 값은 URL 인코딩해요. scope의 공백은 %20이나 +로 보내요.
요청 예시
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=S256import { 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());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도 붙여요.
https://partner.example.com/paysync/callback?code=k3jq8qxvb7rt2mlw9syda1b2c3d4e5f6g7h8i9j0&state=Zx81kQ0aLp
| 파라미터 | 설명 |
|---|---|
code |
인가 코드예요. 고객이 고른 워크스페이스와 허용한 스코프가 담겨 있어요. |
state |
인가 요청에 보낸 state예요. |
인가 코드는 10분 동안 한 번만 쓸 수 있어요. 받은 즉시 토큰 발급으로 바꾸세요.
고객이 취소했을 때
연결 화면에서 취소를 누르면 error=access_denied를 붙여 이동해요. state를 보냈다면 state도 붙여요.
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 없이는 토큰으로 바꿀 수 없어요. 클라이언트 시크릿을 보관하는 서버에서도 함께 쓰는 것을 권장해요.
- 요청마다
A-Z,a-z,0-9,-,.,_,~로 된 43~128자code_verifier를 만들어 세션에 저장해요. code_challenge는BASE64URL(SHA256(code_verifier))이고, 끝의=는 빼요.- 인가 요청에
code_challenge와code_challenge_method=S256을 넣어요. - 토큰 요청에 같은
code_verifier를 넣어요.
32바이트 난수를 base64url로 인코딩하면 43자 code_verifier가 돼요. 위 요청 예시의 Node.js와 Python 코드가 이 방식이에요.