인증
InfraIO Pay에는 인증 모델이 다른 두 가지 API 표면이 있습니다. 호출자가 누구인지에 맞는 것을 선택하세요.
| 표면 | 경로 프리픽스 | 대상 | 인증 |
|---|---|---|---|
| 가맹점 B2B | /b2b/v1/* | 가맹점 서버 | HMAC-SHA256 요청 서명 |
| 대시보드 | 서비스별: /auth/*, /payment/*, /merchant/*, /event/*, /user/*, … | 가맹점 대시보드 브라우저 세션 | Bearer JWT |
이 페이지는 B2B 표면을 다룹니다 — API 키 쌍으로 가맹점 서버에서 호출하는 표면입니다. InfraIO 대시보드를 임베드하거나 내부 도구를 만드는 경우 대시보드 표면을 사용하세요(별도 문서, 아직 공개되지 않음).
게이트웨이는 각 표면을 선행 프리픽스로 라우팅한 후 제거하여 전달합니다.
/b2b/v1/checkout-sessions/quick은 payment-service에 /v1/checkout-sessions/quick
으로 도달하고, 대시보드의 /payment/v1/orders는 /v1/orders로 도달합니다.
따라서 다른 곳에서 /v1/* 베어 경로를 본다면, 그것은 공개 프리픽스가 제거된 후의
백엔드 내부 경로입니다 — 클라이언트는 항상 프리픽스가 붙은 형식을 보냅니다.
(서명에 대한 한 가지 결과: B2B 정규 문자열은 /b2b 프리픽스가 그대로 붙은
경로를 서명합니다 — 아래 참조.)
엔드포인트
| 환경 | 기본 URL |
|---|---|
| 테스트 | https://api-dev.infraio.xyz |
| 라이브 | https://api.infraio.xyz |
동일한 URL 패턴 — 환경은 URL이 아닌 키 프리픽스(pk_test_… vs pk_live_…)로
제어됩니다.
키 쌍
가맹점 대시보드(Developers → API keys → + Add key)에서 두 가지 값을 받습니다.
- 공개 키(
pk_test_…또는pk_live_…) — 계정을 식별합니다.X-Client-ID로 전송됩니다. 브라우저 번들에 임베드해도 안전합니다(SDK가 이미 그렇게 합니다). - 비밀 키(
sk_test_…또는sk_live_…) — HMAC 서명 키. 서버 전용입니다. 데이터베이스 패스워드처럼 다루세요.
비밀 키가 브라우저 번들, git 저장소, 로그 라인, 또는 공유 채팅에 노출된 경우 — 대시보드에서 즉시 폐기하세요. 오버랩 윈도우는 없습니다. 폐기는 즉시 적용됩니다. 새 키를 발급하고 재배포하세요.
요청 서명
/b2b/v1/*에 대한 모든 호출은 세 개의 헤더를 포함합니다.
X-Client-ID: pk_live_70d0becbd7a53047b0dfe88a960d10ad
X-Timestamp: 1715990400
X-Signature: 9a8b7c6d… (hex HMAC-SHA256)서명은 정규 문자열에 대해 계산됩니다.
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODYMETHOD— 대문자 HTTP 동사(POST,GET, …).PATH— 호스트 없이, 쿼리 문자열 없이/b2b프리픽스가 포함된 요청 경로(예:/b2b/v1/checkout-sessions/quick). 게이트웨이는/b2b를 제거하기 전에 원본 인바운드 경로에 대해 서명을 검증하므로 프리픽스가 반드시 있어야 합니다. 쿼리 매개변수는 서명되지 않습니다 —GET …?cursor=…&limit=20의 경우,?…부분이 아닌 경로만 서명하세요.TIMESTAMP— 10진수 문자열로 된 유닉스 초(예:"1715990400"),X-Timestamp와 정확히 일치해야 함.BODY— 원본 요청 본문 바이트.GET/DELETE의 경우 빈 문자열.
비밀 키로 HMAC-SHA256 서명, hex로 출력:
Node / TS
import { createHmac } from "node:crypto";
function sign({ method, path, body, secret }: {
method: string; path: string; body: string; secret: string;
}) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const input = [method.toUpperCase(), path, timestamp, body].join("\n");
const signature = createHmac("sha256", secret).update(input).digest("hex");
return { timestamp, signature };
}왜 HMAC이고 Bearer가 아닌가?
베어 Bearer 토큰 API는 모든 요청에서 유일한 시크릿을 회선을 통해 전송합니다. TLS-종료 프록시 로그 하나를 캡처한 누구나 계정 키를 얻게 됩니다. HMAC 서명은 시크릿이 절대 전송되지 않음을 의미합니다 — 일회용인 파생 서명(해당 정확한 요청 + 해당 정확한 분에 바인딩됨)만 전송됩니다.
트레이드오프: 모든 호출에 대해 서명을 계산해야 합니다. 서버 SDK가 이를 숨기겠지만, 공개될 때까지 위의 헬퍼는 언어당 약 15줄입니다.
타임스탬프 허용 오차
바인딩 허용 오차는 ±5분(300초)이며, 서명을 검증할 때 merchant-service가
강제합니다. 게이트웨이 자체는 방어 차원에서 약간 더 느슨하지만(310초),
게이트웨이를 통과하고 내부 검증에서 실패하는 요청은 여전히
401 invalid_signature로 끝납니다 — 계약상 300초로 가정하세요. 두 가지
시사점:
- NTP로 서버 클록을 동기화하세요. 드리프트된 클록을 가진 장시간 실행 크론은 간헐적으로 실패합니다.
- 서명을 미리 계산하여 큐에 넣지 마세요. 요청이 재시도 큐에 5분 이상 머무르면 서명이 만료됩니다.
키 스코프
비밀 키는 다음 스코프 번들 중 하나 이상을 가집니다.
| 스코프 | 의도된 사용 |
|---|---|
read | 주문, 세션, 환불 목록/읽기 |
write_order | 체크아웃 세션, 주문 생성 |
write_refund | 환불 발행, 환불 요청 토큰 발급 |
webhook_manage | 웹훅 엔드포인트 생성/업데이트/삭제 |
대시보드는 기본적으로 “전체 액세스” 키(네 가지 스코프 모두)를 발급합니다. Developers → API keys → + Add key에서 통합이 필요한 스코프만 체크하여 제한된 스코프 키를 발급할 수 있습니다.
현재 스코프 강제는 권고 사항이며 게이팅되지 않습니다. 스코프는 키에
기록되고 대시보드에 표시되지만, 게이트웨이 미들웨어는 아직 범위 밖의
호출을 거부하지 않습니다 — 모든 유효한 sk_… 키는 오늘 전체 액세스로
동작합니다. 엔드포인트별 스코프 게이팅은 다음 릴리스에 있습니다. 아직
스코프를 보안 경계로 의존하지 마세요 — 그동안은 라벨로 다루고 키를
로테이션/폐기하여 액세스를 제한하세요.
서명이 검증되는 곳
HMAC 검증은 게이트웨이에서 한 번 발생합니다. 게이트웨이는:
X-Client-ID,X-Timestamp,X-Signature를 읽습니다.pk_…로 가맹점 + 시크릿을 조회하고, 타임스탬프 윈도우 검사를 실행하고, 서명을 재계산하고, 상수 시간 비교를 수행합니다.- 성공 시 인증 헤더를 제거하고 내부 헤더를 요청에 스탬프(
X-B2B-Auth: 1,X-Merchant-ID,X-Merchant-Domain)하여 다운스트림 서비스(payment-service, merchant-service 등)로 전달합니다. 환경 + 해결된 스코프는 현재 주입되지 않습니다 — 환경이 필요한 다운스트림 코드는 헤더가 아닌 요청 본문/가맹점별 구성에서 도출합니다. - 실패 시 백엔드를 건드리지 않고 **401
INVALID_SIGNATURE**를 반환합니다.
다운스트림 서비스는 HMAC을 재실행하지 않습니다 — 게이트웨이의 주입된
헤더를 신뢰하고 게이트웨이가 해결한 가맹점에 대해 동작합니다. 엔드포인트별
스코프 게이팅도 하지 않습니다: 위에서 언급한 대로 키의 스코프가
주입되지 않으므로, 인증된 sk_…는 가맹점의 모든 엔드포인트에 도달합니다
(스코프 강제는 현재 권고 사항입니다 — 키 스코프 아래의 콜아웃
참조). 이는 두 가지 방식으로 중요합니다.
- InfraIO Pay 앞에 자체 리버스 프록시를 운영하는 경우,
X-B2B-Auth/X-Merchant-ID를 제거하지 마세요(그리고 위조하지도 마세요 — 게이트웨이는 공개 엣지에서 이를 포함한 인바운드 요청을 거부합니다). - 공개 네트워크 경로(
/b2b/v1/*)는 HMAC 단계를 실행하는 유일한 표면입니다. 서비스 간 내부 gRPC는 mTLS를 사용합니다 —X-Client-ID를 받지 않는 다른 신뢰 모델입니다.
다음 단계
- 오류 — 4xx/5xx 응답 형식.
- 보안 → API 키 — 로테이션, 폐기, 시크릿 유출 시 조치.
- 웹훅 → 서명 검증 — 다른 HMAC
방식을 사용합니다(헤더
X-Signature: sha256=…,X-Timestamp + "." + raw_body서명, 24시간 로테이션 grace 윈도우 동안 선택적X-Signature-Prev). 방식을 혼동하지 마세요 — 해시 알고리즘은 같지만 서명된 바이트와 시크릿 패밀리(whsec_…vssk_…)가 다릅니다.