목차
OAuth PKCE
OAuth PKCE
OAuth 2.0 PKCE (Proof Key for Code Exchange)는 공개 클라이언트 앱(브라우저 SPA, 모바일 앱)이 클라이언트 측에 장기 서버 시크릿을 저장하지 않고도 개별 사용자를 대신하여 API 호출을 할 수 있게 합니다.
OAuth PKCE 사용 시기
- 사용자별 과금이 필요한 브라우저 SPA 또는 모바일 앱 구축 시
- 멀티테넌트 SaaS — 각 최종 사용자가 자체 API 사용량을 지불
- 클라이언트 측에 공유 시크릿을 저장할 수 없는 경우 (공개 클라이언트)
- 단일 공유 API 키 대신 사용자별로 독립적이고 폐기 가능한 키(사용자가 폐기할 때까지 유효)가 필요한 경우
사용자에게 보이는 화면
자체 로그인 화면에서 다음 단계를 안내하세요:
- 앱에서 "BazaarLink로 로그인"(또는 원하는 버튼 이름)을 선택합니다.
- 브라우저에 BazaarLink 승인 페이지가 열립니다. 아직 로그인하지 않았다면 먼저 로그인하고 자동으로 이 페이지로 돌아옵니다.
- 페이지에 앱 주소와 허용했을 때 일어나는 일이 표시됩니다: 앱 전용 새 API 키가 생성되고, 사용량은 BazaarLink 잔액에서 과금되며, 언제든 키를 폐기할 수 있습니다. 키 이름과 월 지출 한도를 지정할 수 있습니다(둘 다 선택 사항).
- "허용"을 클릭하면 앱으로 돌아갑니다. "거부"를 클릭하면 키가 생성되지 않고 앱에도 알리지 않으며 "접근이 거부되었습니다" 페이지가 표시됩니다.
- 나중에 접근을 취소하려면 API 키 페이지(https://bazaarlink.ai/keys)를 열어 앱 이름이 붙은 키를 비활성화하거나 삭제하세요.
동작 방식
단계 1 — 인증 시작 — 랜덤 code_verifier(긴 랜덤 문자열)와 그 SHA-256 해시를 code_challenge로 생성한 다음, 사용자를 BazaarLink 인증 페이지로 리디렉션합니다.
https://bazaarlink.ai/auth?callback_url=https://yourapp.com/callback&code_challenge=CODE_CHALLENGE&code_challenge_method=S256인증 URL 매개변수
- callback_url (필수) — 사용자가 허용한 뒤 돌아올 주소. 규칙은 아래를 참고하세요.
- code_challenge (필수) — code_verifier의 SHA-256을 base64url로 인코딩한 값(43자, 패딩 없음).
- code_challenge_method (필수) — 반드시 S256이어야 합니다.
- state (선택) — 자체 불투명 CSRF 값으로, 출력 가능한 ASCII 문자 1~256자. BazaarLink는 code 바로 뒤에 &state=…로 그대로 돌려줍니다. 형식이 잘못된 state는 승인 페이지에 "잘못된 승인 요청"이 표시되게 합니다.
- key_label (선택) — 최대 100자. 동의 페이지의 키 이름을 미리 채워 줍니다(사용자가 변경할 수 있음). 너무 길거나 빈 라벨은 무시됩니다.
- Headless tools (CLI / desktop, no callback_url) — omit callback_url. After the user allows, the page shows a one-time code (valid 5 minutes, single use) for them to enter into your tool; exchange it exactly like a redirected code. The page cannot show which app is asking, so tell users to enter the code only in a tool they started themselves.
지원하지 않음
- workspace_id와 required_workspace_id — 지원하지 않습니다. 키는 항상 사용자 본인 계정에 속합니다.
- code_challenge_method=plain — 지원하지 않습니다(S256만 가능).
callback_url 규칙
- https://여야 합니다. http://는 localhost, 127.0.0.1, [::1](모든 포트)에만 허용되며, 이 경우 동의 페이지에 로컬 개발 경고가 표시됩니다.
- 프래그먼트(#), URL 내 사용자 이름·비밀번호는 허용되지 않으며 최대 2048자입니다.
- 동의 페이지에 callback 호스트 이름이 표시되어 사용자가 어떤 앱이 요청하는지 알 수 있습니다.
- 사용자가 허용하면 브라우저가 code=…가 덧붙은 callback_url로 이동합니다(state를 보냈다면 state=…도 덧붙으며, 기존 쿼리 문자열은 유지됩니다). code는 10분간 유효하며 한 번만 사용할 수 있습니다.
단계 2 — 코드를 API 키로 교환 — 사용자가 요청을 승인하면 BazaarLink이 일회용 code와 함께 callback_url로 리디렉션합니다. code는 10분간 유효하며 한 번만 사용할 수 있고, code_verifier가 틀리면 code가 폐기됩니다. code, code_verifier, code_challenge_method(세 가지 모두 필수)를 POST하면 해당 사용자의 API 키를 받을 수 있습니다.
단계 3 — 사용자 범위 키 사용 — 반환된 키는 승인한 사용자에게 속한 일반 BazaarLink API 키입니다. 사용량은 귀하가 아니라 해당 사용자의 잔액에서 과금됩니다.
curl https://bazaarlink.ai/api/v1/chat/completions \
-H "Authorization: Bearer sk-bl-..." \
-H "Content-Type: application/json" \
-d '{"model": "google/gemini-3.5-flash", "messages": [{"role": "user", "content": "Hello"}]}'JavaScript 예제 (브라우저)
/auth 페이지와 POST /api/v1/auth/keys는 https://bazaarlink.ai 에서 제공됩니다(api.bazaarlink.ai 아님). 발급받은 키는 https://bazaarlink.ai/api/v1 과 https://api.bazaarlink.ai/v1 두 기본 URL 모두에서 작동합니다.
사용자가 승인 시 지출 한도를 설정하면 그 한도는 월 한도이며 계정의 월간 주기에 맞춰 초기화됩니다.
오류
오류는 OpenAI 방식의 {"error":{"message","type","code"}} 형식입니다. POST /api/v1/auth/keys는 다음을 반환할 수 있습니다:
- 400 invalid_code_challenge_method — code_challenge_method가 없거나 plain이거나 S256이 아닙니다.
- 400 — JSON 본문이 올바르지 않거나 code / code_verifier가 없습니다.
- 403 invalid_code — code를 알 수 없거나 만료되었거나 이미 사용되었거나, code_verifier가 틀렸거나, 계정이 더 이상 허용되지 않습니다. 호출자가 구분할 수 없도록 모두 의도적으로 같은 메시지를 반환합니다. code_verifier가 틀리면 code도 폐기되므로 사용자에게 다시 승인하도록 요청하세요.
- 429 rate_limited — 한 IP에서 10분 동안 30회를 초과해 교환을 시도했습니다(Retry-After 헤더 참고).
- 429 key_total_cap — 계정이 이미 최대 200개의 API 키를 보유하고 있습니다.
- 405 — POST만 허용됩니다.
서버에서 인증 코드 생성하기 (관리 키)
개인 관리 키(Management Key)의 소유자는 브라우저 인증 페이지를 거치지 않고 POST /api/v1/auth/keys/code 로 직접 인증 코드를 생성할 수 있습니다(OpenRouter와 호환). 코드는 해당 관리 키의 소유자에게 묶이므로, 교환해서 받는 키는 그 소유자 계정의 표준 키이며 한도, 주기, 만료 시각, 이름은 여기에서 지정한 값이 적용됩니다. 자체 도구가 이미 관리 키를 보유하고 있고 앱에 제한된 키를 발급하려는 경우에 사용합니다. 교환 단계(POST /api/v1/auth/keys)는 위와 동일합니다.
- callback_url (필수) 및 code_challenge + code_challenge_method=S256 (필수) — 인증 URL과 동일한 규칙입니다.
- key_label (선택) — 최대 100자, 키 이름. 기본값: "App: <콜백 호스트>".
- limit (선택) — USD 사용 한도, 0 이상. 0 또는 생략하면 한도 없음입니다.
- usage_limit_type (선택) — daily, weekly, monthly: 한도가 초기화되는 주기. limit과 함께 사용할 때만 적용되며 기본값은 monthly입니다.
- expires_at (선택) — 키 만료 시각: 미래의 ISO 8601 UTC 시각(초 단위 포함), 예: 2026-12-31T23:59:59Z. 분 단위까지만 있는 값은 거부됩니다. 생략하면 키는 폐기할 때까지 유효합니다.
- workspace_id — 지원하지 않음 (400). spawn_agent 와 spawn_cloud 는 허용되지만 무시됩니다.
응답: { "data": { "id", "app_id", "created_at" } }. id 는 교환에 사용하는 일회용 코드입니다(10분간 유효, 1회만 사용). BazaarLink에는 앱 등록 체계가 없으므로 app_id 는 콜백 주소에서 도출한 고정 정수입니다. 같은 주소는 항상 같은 app_id 를 받습니다.
- 401 unauthorized — Bearer 키가 없거나 알 수 없는 키입니다.
- 403 management_key_required — 개인 관리 키가 아닙니다(표준 키와 조직 관리 키는 거부됩니다).
- 403 account_not_allowed — 키 소유자가 정지되었거나 삭제되었습니다.
- 400 — invalid_callback_url, invalid_code_challenge_method, invalid_code_challenge, invalid_expires_at, workspace_not_supported 또는 잘못된 본문.
- 429 rate_limited — 한 소유자가 시간당 30개 초과 또는 한 IP에서 시간당 120개 초과의 코드를 생성했습니다(Retry-After 참조).
발급되는 키
- 표준 BazaarLink API 키(sk-bl-…)로, API 키 페이지에서 만든 키와 같은 종류입니다. 사용자가 폐기할 때까지 유효합니다.
- 사용량은 사용자의 BazaarLink 잔액에서 과금됩니다. 사용자가 동의 페이지에서 지출 한도를 설정했다면 월 한도입니다.
- 두 기본 URL 모두에서 작동합니다: https://bazaarlink.ai/api/v1 및 https://api.bazaarlink.ai/v1.
- 계정의 키 한도 200개에 포함되며 사용자의 API 키 목록에 표시되어, 사용자가 언제든 비활성화하거나 삭제할 수 있습니다.
- 정지되었거나 삭제된 계정은 앱을 승인할 수 없으며, 정지 전에 발급된 code는 교환 시 거부됩니다(403 invalid_code).
제공되는 것
- 공개 클라이언트 애플리케이션을 위한 Authorization Code + PKCE 흐름
- 사용자별 과금: 각 사용자가 자신의 잔액으로 자신의 사용량을 지불
- 사용자마다 별도의 키가 발급되며, 사용자의 API 키 페이지에 표시되어 직접 폐기할 수 있음