Customer API 인증 가이드 (OIDC)
Customer API는 OpenID Connect(OIDC) 기반 인증을 사용합니다. 쇼핑몰 관리자가 아닌 고객(회원) 본인 자격으로 API를 호출한다는 점이 Admin API와의 근본적인 차이입니다.
📚 목차
- Customer API 개요
- Admin API와의 차이
- 인증 흐름도
- 사용 전제 조건
- Step 1: 인증 코드 요청
- Step 2: 토큰 발급
- Step 3: API 호출
- id_token 검증
- Scope 정책
- 토큰 갱신 및 폐기
- OIDC Discovery / 공개키(JWKS)
- 에러 처리
🔑 Customer API 개요
Customer API는 로그인한 쇼핑몰 회원이 자신의 정보와 주문에 접근하는 API입니다.
| 구분 | 자격 | 접근 범위 |
|---|---|---|
| Admin API | 쇼핑몰 관리자 | 쇼핑몰 전체 데이터 |
| Front API | 익명/공개 | 상품·분류 등 공개 정보 |
| Customer API | 쇼핑몰 회원 본인 | 본인의 회원정보·주문 |
제공 엔드포인트
| 그룹 | 엔드포인트 | 필요 Scope |
|---|---|---|
| 회원정보 | GET /customers/userinfo | openid |
| 주문 조회 | GET /customers/orders, GET /customers/orders/{order_id}, GET /customers/orders/{order_id}/items | read_customer_order |
| 취소 처리 | POST /customers/cancellationrequests, POST /customers/cancellation | write_customer_order |
Base URL은 다른 API와 동일하게 https://{mall_id}.cafe24api.com/api/v2 입니다.
각 엔드포인트의 요청·응답 상세는 Customer API 레퍼런스를 참고하세요.
🔍 Admin API와의 차이
| 항목 | Admin API | Customer API |
|---|---|---|
| 인증 주체 | 쇼핑몰 관리자 | 쇼핑몰 회원 |
| 발급 토큰 | access_token, refresh_token | access_token, refresh_token, id_token |
| PKCE | 미지원 | 지원 (특정 클라이언트 프로파일은 필수) |
nonce | 미사용 | 지원 (재생 공격 방지) |
| 필수 Scope | 없음 | openid 필수 |
| Scope 체계 | mall.read_order 등 | openid, read_customer_order, write_customer_order |
| 인증 엔드포인트 | /api/v2/oauth/authorize, /api/v2/oauth/token | 동일 |
인증 엔드포인트 자체는 Admin API와 같습니다. 요청 Scope에 openid가 포함되고 앱이 OIDC 사용으로 설정되어 있을 때 OIDC 흐름으로 동작합니다.
🔄 인증 흐름도
사전 조건: 앱이 해당 쇼핑몰에 설치·활성화되어 있어야 합니다.
✅ 사용 전제 조건
| 조건 | 설명 |
|---|---|
| 앱 OIDC 사용 설정 | 개발자센터에서 앱이 OIDC를 사용할 수 있도록 설정되어 있어야 합니다 |
openid Scope 요청 | Scope에 openid가 없으면 OIDC로 동작하지 않고 id_token도 발급되지 않습니다 |
| 회원 세션 | 쇼핑몰 회원으로 로그인한 세션이어야 합니다 |
운영자(관리자) 권한 세션에서는 openid Scope가 자동으로 제거됩니다.
그 결과 OIDC 흐름이 성립하지 않고 id_token도 발급되지 않습니다. 반드시 회원 계정으로 인증하세요.
🎯 Step 1: 인증 코드 요청
API 호출에 필요한 Access Token을 발급받기 위해 먼저 인증 코드(code)를 발급받아야 합니다.
code는 발급 후 1분이 지나면 만료됩니다.- Access Token 발급에 사용한
code는 재사용할 수 없습니다.
⚠️ 중요: 코드 요청은 웹 브라우저에서만 진행하세요. cURL이나 프로그래밍 언어로 직접 요청하면 안 됩니다.
파라미터
| 파라미터 | 필수 | 설명 |
|---|---|---|
response_type | ✅ | 고정값 code |
client_id | ✅ | 개발자센터에서 앱 생성 시 발급받은 App Key(Client ID) |
redirect_uri | ✅ | code를 수신할 URL. 개발자센터에 등록한 Redirect URL 중 하나와 정확히 일치해야 합니다 |
scope | ✅ | openid 포함 필수. 공백 또는 ,로 구분 |
state | 권장 | CSRF 방지용 임의 문자열. 리다이렉트 시 그대로 전달되므로 반드시 검증하세요 |
nonce | 권장 | 재생 공격 방지용 임의 문자열. id_token의 nonce 클레임에 그대로 담겨 반환됩니다 |
code_challenge | 조건부 | PKCE 적용 대상 클라이언트는 필수. BASE64URL(SHA256(code_verifier)) |
code_challenge_method | 조건부 | S256만 허용 (plain 거부) |
prompt | 선택 | consent · login · none 중 하나. none은 단독 사용만 가능 |
prompt 옵션
| 값 | 동작 |
|---|---|
consent | 이미 동의했더라도 권한 동의 화면을 다시 표시 (예: 약관 변경 시) |
login | 로그인 세션이 있어도 다시 로그인하도록 강제 |
none | 로그인 세션·권한 동의가 있으면 UI 없이 상호작용 생략 |
요청 URL
https://{mall_id}.cafe24api.com/api/v2/oauth/authorize
?response_type=code
&client_id={client_id}
&state={state}
&redirect_uri={redirect_uri}
&scope=openid%20read_customer_order
&nonce={nonce}
&code_challenge={code_challenge}
&code_challenge_method=S256
로그인 이력이 없으면 로그인 화면으로 이동합니다. 로그인 세션은 2시간 동안 유효하며, 그 안에 로그인한 적이 있으면 바로 동의 화면이 표시됩니다.
PKCE 적용 대상
다음 클라이언트 프로파일이면서 개발자센터에서 PKCE 사용으로 설정된 앱은 code_challenge가 필수입니다.
| 클라이언트 프로파일 | PKCE |
|---|---|
android | 필수 |
ios | 필수 |
single-page-application | 필수 |
ai-agent | 필수 |
| 그 외 (서버 사이드 앱 등) | 미적용 |
code_challenge_method는S256만 허용됩니다.code_verifier는A-Z a-z 0-9 - . _ ~문자로 구성된 43~128자 문자열이어야 합니다.- WebView 환경에서는 인증 요청이 차단됩니다. 모바일 앱은 시스템 브라우저(Android Custom Tabs, iOS SFSafariViewController)를 사용하세요.
code_verifier / code_challenge 생성 — Node.js
const crypto = require('crypto');
// code_verifier: 43~128자
const codeVerifier = crypto.randomBytes(32).toString('base64url'); // 43자
// code_challenge = BASE64URL(SHA256(code_verifier))
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// nonce: 재생 공격 방지용 임의 값
const nonce = crypto.randomBytes(16).toString('hex');
// code_verifier와 nonce는 토큰 교환/검증 시 필요하므로 세션에 보관
req.session.codeVerifier = codeVerifier;
req.session.nonce = nonce;
code_verifier / code_challenge 생성 — Python
import hashlib
import base64
import secrets
# code_verifier: 43~128자
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b'=').decode()
# code_challenge = BASE64URL(SHA256(code_verifier))
digest = hashlib.sha256(code_verifier.encode()).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b'=').decode()
# nonce: 재생 공격 방지용 임의 값
nonce = secrets.token_hex(16)
session['code_verifier'] = code_verifier
session['nonce'] = nonce
응답
code를 쿼리 스트링에 담아 redirect_uri로 리다이렉트합니다.
HTTP/1.1 302 Found
Location: {redirect_uri}?code={authorize_code}&state={state}
| 키 | 설명 |
|---|---|
code | 인증 코드 (1분 유효, 1회용) |
state | 요청에 보낸 값이 그대로 반환됩니다. 요청 시 값과 일치하는지 반드시 확인하세요 |
에러 응답
오류도 redirect_uri로 전달됩니다.
HTTP/1.1 302 Found
Location: {redirect_uri}?error={error}&error_description={description}&state={state}&trace_id={trace_id}
| 오류 코드 | 발생 사례 | 해결 방법 |
|---|---|---|
invalid_request | client_id, redirect_uri, scope 누락 또는 값 오류 | 개발자센터의 앱 정보와 요청값을 대조하세요 |
unsupported_response_type | response_type이 누락되었거나 code가 아님 | response_type=code로 지정하세요 |
invalid_scope | 요청 범위가 잘못되었거나 형식 오류 | 개발자센터에 등록한 권한 정보대로 요청하세요 |
access_denied | 사용자가 동의를 거부했거나 인증 불가 계정 | 회원 계정으로 다시 인증하도록 안내하세요 |
trace_id는 오류 추적용 고유 일련번호입니다. 문의 시 함께 전달하면 원인 파악이 빨라집니다.
🎯 Step 2: 토큰 발급
Step 1에서 받은 code를 Access Token으로 교환합니다.
파라미터
| 파라미터 | 필수 | 설명 |
|---|---|---|
grant_type | ✅ | 고정값 authorization_code |
code | ✅ | Step 1에서 받은 인증 코드 |
redirect_uri | ✅ | Step 1과 동일한 값 |
code_verifier | 조건부 | PKCE 적용 대상 클라이언트는 필수. Step 1의 code_challenge를 만들 때 사용한 원본 값 |
요청
curl -X POST \
'https://{mall_id}.cafe24api.com/api/v2/oauth/token' \
-H 'Authorization: Basic {base64_encode({client_id}:{client_secret})}' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code&code={code}&redirect_uri={redirect_uri}&code_verifier={code_verifier}'
성공 응답 (200 OK)
{
"access_token": "0iqR5nM5EJIq..........",
"expires_at": "2026-08-10T14:00:00.000",
"refresh_token": "JeTJ7XpnFC0P..........",
"refresh_token_expires_at": "2026-08-24T12:00:00.000",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOi...",
"client_id": "BrIfqEKoPxeE..........",
"mall_id": "yourmall",
"user_id": "ecu2109510374",
"scopes": ["openid", "read_customer_order"],
"issued_at": "2026-08-10T12:00:00.000",
"shop_no": "1",
"token_type": "Bearer"
}
| 키 | 설명 |
|---|---|
access_token | API 호출에 사용하는 토큰 (2시간 유효) |
expires_at | Access Token 만료 일시 |
refresh_token | Access Token 재발급용 토큰 (2주 유효) |
refresh_token_expires_at | Refresh Token 만료 일시 |
id_token | 회원 식별용 JWT. 평문 개인정보는 포함하지 않으며, 필요 시 GET /customers/userinfo로 조회 |
scopes | 사용자가 동의한 Scope 목록 |
Admin API 응답과 비교해 id_token이 추가됩니다. 유효 기간 정책은 동일합니다.
에러 응답
HTTP/1.1 400 Bad Request
{
"error": "invalid_grant",
"error_description": "It is a wrong code."
}
| 오류 코드 | HTTP | 발생 사례 | 해결 방법 |
|---|---|---|---|
invalid_client | 401 | client_id/client_secret 누락 또는 오류 | Authorization: Basic 헤더와 앱 정보를 확인하세요 |
invalid_request | 400 | redirect_uri, code 누락 | 누락된 값을 확인하세요 |
unsupported_grant_type | 400 | grant_type이 authorization_code가 아님 | grant_type=authorization_code로 지정하세요 |
invalid_grant | 400 | 잘못된 redirect_uri, 잘못되었거나 만료·재사용된 code | Step 1부터 다시 진행해 새 code를 발급받으세요 |
🎯 Step 3: API 호출
발급받은 Access Token을 Authorization: Bearer 헤더에 담아 호출합니다.
curl -X GET \
'https://{mall_id}.cafe24api.com/api/v2/customers/orders' \
-H 'Authorization: Bearer {access_token}' \
-H 'Content-Type: application/json'
서버는 토큰과 클레임(iss, aud, exp, scope)을 검증하고, 토큰의 회원(sub)이 요청한 리소스의 소유자인지 확인한 뒤 본인 데이터만 응답합니다.
🛡️ id_token 검증
id_token은 RS256으로 서명된 JWT이며 다음 클레임을 포함합니다.
| 클레임 | 설명 |
|---|---|
iss | 토큰 발행처(Issuer). https://{mall_id}.cafe24api.com |
aud | 토큰 수신 대상 — 요청한 client_id |
sub | 회원 식별자 |
exp / iat | 만료 시각 / 발급 시각 |
auth_time | 인증이 수행된 시각 |
amr | 인증 수단. 현재는 비밀번호 인증만 지원하므로 ["pwd"] 고정 |
nonce | Step 1에서 nonce를 보낸 경우에만 포함 |
at_hash | Access Token 해시 |
검증 절차
애플리케이션은 id_token을 신뢰하기 전에 반드시 다음을 확인해야 합니다.
| # | 검증 항목 | 확인 내용 |
|---|---|---|
| 1 | 서명 | JWT 헤더의 kid에 해당하는 공개키(JWKS)로 서명 검증 |
| 2 | iss | https://{mall_id}.cafe24api.com과 일치 |
| 3 | aud | 본인 앱의 client_id와 일치 |
| 4 | nonce | Step 1에서 보낸 값과 일치 |
| 5 | exp | 만료되지 않았는지 확인 |
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const client = jwksClient({
jwksUri: `https://${MALL_ID}.cafe24api.com/api/v2/.well-known/jwks.json`,
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
callback(err, key && key.getPublicKey());
});
}
jwt.verify(
idToken,
getKey,
{
algorithms: ['RS256'],
issuer: `https://${MALL_ID}.cafe24api.com`,
audience: CLIENT_ID,
},
(err, payload) => {
if (err) throw err;
// nonce 검증 (Step 1에서 nonce를 보낸 경우)
if (payload.nonce !== req.session.nonce) {
throw new Error('id_token의 nonce가 일치하지 않습니다 — 재생 공격 가능성');
}
},
);
nonce를 보냈다면 반드시 대조하세요. 검증을 생략하면 재생 공격 방지 효과가 사라집니다.
🔐 Scope 정책
사용 가능한 Scope
| Scope | 용도 |
|---|---|
openid | 필수. OIDC 흐름 활성화 및 id_token 발급 |
read_customer_order | 본인 주문 조회 |
write_customer_order | 본인 주문 취소 처리 |
조합 규칙
✅ openid
✅ openid read_customer_order
✅ openid read_customer_order write_customer_order
❌ read_customer_order → openid 누락
❌ openid mall.read_order → 관리자 Scope 혼용 불가
❌ openid mall.write_product → 관리자 Scope 혼용 불가
| 규칙 | 내용 |
|---|---|
openid 필수 | 포함되지 않으면 OIDC 흐름으로 동작하지 않음 |
| 관리자 Scope 혼용 불가 | mall.read_* / mall.write_* 계열은 함께 사용할 수 없음 |
Customer API 엔드포인트를 openid Scope 없는 토큰으로 호출하면 403 Forbidden (insufficient_scope)이 반환됩니다.
🔄 토큰 갱신 및 폐기
Admin API와 동일한 엔드포인트·방식을 사용합니다.
갱신
Refresh Token은 2주간 유효하며, 교환 시 새 Refresh Token이 발급되고 기존 값은 무효화됩니다.
curl -X POST \
'https://{mall_id}.cafe24api.com/api/v2/oauth/token' \
-H 'Authorization: Basic {base64_encode({client_id}:{client_secret})}' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=refresh_token&refresh_token={refresh_token}'
| 오류 코드 | HTTP | 발생 사례 |
|---|---|---|
invalid_client | 401 | client_id/client_secret 누락 또는 오류 |
invalid_request | 400 | refresh_token 누락 |
unsupported_grant_type | 400 | grant_type이 refresh_token이 아님 |
invalid_grant | 400 | 잘못되었거나 만료된 refresh_token (이미 교환된 이전 값 포함) → Step 1부터 재인증 |
폐기
요청한 토큰에 연결된 Refresh Token도 함께 폐기됩니다. token_hint를 생략하면 access_token으로 간주합니다.
curl -X POST \
'https://{mall_id}.cafe24api.com/api/v2/oauth/revoke' \
-H 'Authorization: Basic {base64_encode({client_id}:{client_secret})}' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'token={token}&token_hint=access_token'
| 파라미터 | 필수 | 설명 |
|---|---|---|
token | ✅ | 폐기할 토큰 |
token_hint | 선택 | access_token · refresh_token · id_token |
성공 시 200 OK에 본문 없이 응답합니다. 잘못된 토큰으로 요청해도 200을 반환합니다.
자세한 내용은 OAuth 2.0 인증 가이드 — 토큰 갱신을 참고하세요.
🌐 OIDC Discovery / 공개키(JWKS)
OIDC 표준 메타데이터와 서명 검증용 공개키를 제공합니다. 별도 인증 없이 호출할 수 있습니다.
Discovery 문서
curl 'https://{mall_id}.cafe24api.com/api/v2/.well-known/openid-configuration'
{
"issuer": "https://{mall_id}.cafe24api.com",
"authorization_endpoint": "https://{mall_id}.cafe24api.com/api/v2/oauth/authorize",
"token_endpoint": "https://{mall_id}.cafe24api.com/api/v2/oauth/token",
"userinfo_endpoint": "https://{mall_id}.cafe24api.com/api/v2/customers/userinfo",
"revocation_endpoint": "https://{mall_id}.cafe24api.com/api/v2/oauth/revoke",
"jwks_uri": "https://{mall_id}.cafe24api.com/api/v2/.well-known/jwks.json",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"token_endpoint_auth_methods_supported": ["client_secret_basic"],
"code_challenge_methods_supported": ["S256"]
}
공개키(JWKS)
id_token 헤더의 kid와 일치하는 키로 서명을 검증합니다. 공개키는 주기적으로 로테이션되며 현재 키와 다음 키가 함께 제공되므로, kid 기준으로 키를 조회하고 캐시를 갱신할 수 있게 구현하세요.
curl 'https://{mall_id}.cafe24api.com/api/v2/.well-known/jwks.json'
{
"keys": [
{ "kty": "RSA", "use": "sig", "alg": "RS256", "kid": "oTZnkfWCCKAK7k8bJIiAqF", "n": "0V2F...", "e": "AQAB" },
{ "kty": "RSA", "use": "sig", "alg": "RS256", "kid": "F31cpCeCUCkOnLVwLQJQKF", "n": "oWry...", "e": "AQAB" }
]
}
🐛 에러 처리
| 상태 | 오류 | 원인 | 해결 |
|---|---|---|---|
| 400 | invalid_request — code_challenge 누락 | PKCE 대상 클라이언트인데 파라미터 미전달 | Step 1에 code_challenge 포함 |
| 400 | invalid_request — code_challenge_method | S256 이외 값 사용 | S256으로 지정 |
| 400 | invalid_request — code_verifier 누락 | 토큰 교환 시 파라미터 미전달 | Step 2에 code_verifier 포함 |
| 400 | invalid_request — code_verifier 형식 오류 | 43~128자 범위를 벗어나거나 허용되지 않은 문자 포함 | 허용 문자(A-Z a-z 0-9 - . _ ~)로 43~128자 생성 |
| 400 | 인증 요청 차단 | WebView에서 인증 시도 | 시스템 브라우저로 전환 |
| 401 | invalid_client | Authorization: Basic 헤더 누락 또는 앱 정보 오류 | 개발자센터의 Client ID/Secret 확인 |
| 403 | insufficient_scope | openid Scope 없이 Customer API 호출 | Scope에 openid 추가 후 재인증 |
| — | id_token 미발급 | 운영자 계정 인증 또는 openid Scope 누락 | 회원 계정으로 openid 포함 재인증 |
기타 공통 상태 코드는 API Status Code 가이드를 참고하세요.
📚 관련 문서
- Customer API 레퍼런스 — 엔드포인트별 요청·응답
- OAuth 2.0 인증 가이드 — Admin/Front API 인증
- API 쿼터 가이드 — 호출 제한 및 429 대응
- API Status Code 가이드 — 상태 코드 전체