Customer API Authentication Guide (OIDC)
The Customer API uses OpenID Connect (OIDC)-based authentication. The fundamental difference from the Admin API is that the API is called as the customer (member) themselves, not as the shopping mall administrator.
📚 Table of Contents
- Customer API Overview
- Differences from Admin API
- Authentication Flow
- Prerequisites
- Step 1: Request Authorization Code
- Step 2: Issue Token
- Step 3: API Call
- id_token Validation
- Scope Policy
- Token Refresh and Revoke
- OIDC Discovery / Public Keys (JWKS)
- Error Handling
🔑 Customer API Overview
The Customer API lets a logged-in shopping mall member access their own information and orders.
| Category | Identity | Access Scope |
|---|---|---|
| Admin API | Shopping mall administrator | All shopping mall data |
| Front API | Anonymous / public | Public information such as products and categories |
| Customer API | Shopping mall member (self) | Own member information and orders |
Available Endpoints
| Group | Endpoint | Required Scope |
|---|---|---|
| Member information | GET /customers/userinfo | openid |
| Order retrieval | GET /customers/orders, GET /customers/orders/{order_id}, GET /customers/orders/{order_id}/items | read_customer_order |
| Cancellation | POST /customers/cancellationrequests, POST /customers/cancellation | write_customer_order |
The Base URL is https://{mall_id}.cafe24api.com/api/v2, the same as other APIs.
See the Customer API Reference for request and response details of each endpoint.
🔍 Differences from Admin API
| Item | Admin API | Customer API |
|---|---|---|
| Authenticating party | Shopping mall administrator | Shopping mall member |
| Issued tokens | access_token, refresh_token | access_token, refresh_token, id_token |
| PKCE | Not supported | Supported (required for certain client profiles) |
nonce | Not used | Supported (replay attack prevention) |
| Required Scope | None | openid required |
| Scope scheme | mall.read_order, etc. | openid, read_customer_order, write_customer_order |
| Authentication endpoints | /api/v2/oauth/authorize, /api/v2/oauth/token | Same |
The authentication endpoints themselves are the same as the Admin API. The OIDC flow is used when the requested Scope includes openid and the app is configured to use OIDC.
🔄 Authentication Flow
Precondition: The app must be installed and activated on the shopping mall.
✅ Prerequisites
| Condition | Description |
|---|---|
| App configured for OIDC | The app must be configured in the Developer Center to use OIDC |
openid Scope requested | Without openid in the Scope, the OIDC flow is not used and no id_token is issued |
| Member session | The session must be logged in as a shopping mall member |
In an operator (administrator) session, the openid Scope is automatically removed.
As a result, the OIDC flow does not take effect and no id_token is issued. Always authenticate with a member account.
🎯 Step 1: Request Authorization Code
To obtain the Access Token needed for API calls, you first need an authorization code (code).
- The
codeexpires 1 minute after issuance. - A
codethat has already been used to issue an Access Token cannot be reused.
⚠️ Important: Code requests should ONLY be made in a web browser. Do not request directly using cURL or programming languages.
Parameters
| Parameter | Required | Description |
|---|---|---|
response_type | ✅ | Fixed value code |
client_id | ✅ | App Key (Client ID) issued when the app was created in the Developer Center |
redirect_uri | ✅ | URL that receives the code. Must exactly match one of the Redirect URLs registered in the Developer Center |
scope | ✅ | Must include openid. Separated by spaces or , |
state | Recommended | Random string for CSRF prevention. It is returned as-is on redirect, so always verify it |
nonce | Recommended | Random string for replay attack prevention. Returned as-is in the nonce claim of the id_token |
code_challenge | Conditional | Required for clients subject to PKCE. BASE64URL(SHA256(code_verifier)) |
code_challenge_method | Conditional | Only S256 is allowed (plain is rejected) |
prompt | Optional | One of consent · login · none. none can only be used alone |
prompt options
| Value | Behavior |
|---|---|
consent | Shows the permission consent screen again even if already consented (e.g. when terms change) |
login | Forces the user to log in again even if a login session exists |
none | Skips all UI interaction if a login session and permission consent already exist |
Request 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
If there is no login history, the user is taken to the login screen. A login session is valid for 2 hours; if the user has logged in within that time, the consent screen is shown immediately.
Clients Subject to PKCE
code_challenge is required for apps that have one of the following client profiles and are configured to use PKCE in the Developer Center.
| Client Profile | PKCE |
|---|---|
android | Required |
ios | Required |
single-page-application | Required |
ai-agent | Required |
| Others (server-side apps, etc.) | Not applied |
- Only
S256is allowed forcode_challenge_method. code_verifiermust be a string of 43 to 128 characters consisting ofA-Z a-z 0-9 - . _ ~.- Authentication requests are blocked in WebView environments. Mobile apps should use the system browser (Android Custom Tabs, iOS SFSafariViewController).
Generating code_verifier / code_challenge — Node.js
const crypto = require('crypto');
// code_verifier: 43 to 128 characters
const codeVerifier = crypto.randomBytes(32).toString('base64url'); // 43 characters
// code_challenge = BASE64URL(SHA256(code_verifier))
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// nonce: random value for replay attack prevention
const nonce = crypto.randomBytes(16).toString('hex');
// Keep code_verifier and nonce in the session; they are needed for token exchange/validation
req.session.codeVerifier = codeVerifier;
req.session.nonce = nonce;
Generating code_verifier / code_challenge — Python
import hashlib
import base64
import secrets
# code_verifier: 43 to 128 characters
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: random value for replay attack prevention
nonce = secrets.token_hex(16)
session['code_verifier'] = code_verifier
session['nonce'] = nonce
Response
The server redirects to redirect_uri with the code in the query string.
HTTP/1.1 302 Found
Location: {redirect_uri}?code={authorize_code}&state={state}
| Key | Description |
|---|---|
code | Authorization code (valid for 1 minute, single use) |
state | The value sent in the request is returned as-is. Always confirm that it matches the value you sent |
Error Response
Errors are also delivered to redirect_uri.
HTTP/1.1 302 Found
Location: {redirect_uri}?error={error}&error_description={description}&state={state}&trace_id={trace_id}
| Error Code | Cause | Solution |
|---|---|---|
invalid_request | client_id, redirect_uri, or scope is missing or invalid | Compare the request values against the app information in the Developer Center |
unsupported_response_type | response_type is missing or is not code | Specify response_type=code |
invalid_scope | The requested scope is invalid or malformed | Request only the permissions registered in the Developer Center |
access_denied | The user declined consent, or the account cannot be authenticated | Guide the user to authenticate again with a member account |
trace_id is a unique serial number for error tracking. Including it in support inquiries speeds up diagnosis.
🎯 Step 2: Issue Token
Exchange the code received in Step 1 for an Access Token.
Parameters
| Parameter | Required | Description |
|---|---|---|
grant_type | ✅ | Fixed value authorization_code |
code | ✅ | Authorization code received in Step 1 |
redirect_uri | ✅ | Same value as in Step 1 |
code_verifier | Conditional | Required for clients subject to PKCE. The original value used to create the code_challenge in Step 1 |
Request
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}'
Success Response (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"
}
| Key | Description |
|---|---|
access_token | Token used for API calls (valid for 2 hours) |
expires_at | Access Token expiration time |
refresh_token | Token for reissuing the Access Token (valid for 2 weeks) |
refresh_token_expires_at | Refresh Token expiration time |
id_token | JWT for member identification. Contains no plaintext personal information; retrieve it via GET /customers/userinfo when needed |
scopes | List of Scopes the user consented to |
Compared with the Admin API response, id_token is added. The validity period policy is the same.
Error Response
HTTP/1.1 400 Bad Request
{
"error": "invalid_grant",
"error_description": "It is a wrong code."
}
| Error Code | HTTP | Cause | Solution |
|---|---|---|---|
invalid_client | 401 | client_id/client_secret is missing or invalid | Check the Authorization: Basic header and the app information |
invalid_request | 400 | redirect_uri or code is missing | Check for missing values |
unsupported_grant_type | 400 | grant_type is not authorization_code | Specify grant_type=authorization_code |
invalid_grant | 400 | Wrong redirect_uri, or the code is invalid, expired, or already used | Start over from Step 1 to obtain a new code |
🎯 Step 3: API Call
Call the API with the issued Access Token in the Authorization: Bearer header.
curl -X GET \
'https://{mall_id}.cafe24api.com/api/v2/customers/orders' \
-H 'Authorization: Bearer {access_token}' \
-H 'Content-Type: application/json'
The server validates the token and its claims (iss, aud, exp, scope), confirms that the token's member (sub) owns the requested resource, and returns only that member's data.
🛡️ id_token Validation
The id_token is a JWT signed with RS256 and contains the following claims.
| Claim | Description |
|---|---|
iss | Token issuer. https://{mall_id}.cafe24api.com |
aud | Intended audience of the token — the requesting client_id |
sub | Member identifier |
exp / iat | Expiration time / issuance time |
auth_time | Time when authentication was performed |
amr | Authentication method. Fixed to ["pwd"] since only password authentication is currently supported |
nonce | Included only when a nonce was sent in Step 1 |
at_hash | Access Token hash |
Validation Procedure
Your application must verify the following before trusting an id_token.
| # | Check | Details |
|---|---|---|
| 1 | Signature | Verify the signature with the public key (JWKS) matching the kid in the JWT header |
| 2 | iss | Matches https://{mall_id}.cafe24api.com |
| 3 | aud | Matches your app's client_id |
| 4 | nonce | Matches the value sent in Step 1 |
| 5 | exp | Confirm the token has not expired |
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 validation (if a nonce was sent in Step 1)
if (payload.nonce !== req.session.nonce) {
throw new Error('id_token nonce mismatch — possible replay attack');
}
},
);
If you sent a nonce, always compare it. Skipping this check removes the protection against replay attacks.
🔐 Scope Policy
Available Scopes
| Scope | Purpose |
|---|---|
openid | Required. Enables the OIDC flow and issues the id_token |
read_customer_order | Retrieve own orders |
write_customer_order | Cancel own orders |
Combination Rules
✅ openid
✅ openid read_customer_order
✅ openid read_customer_order write_customer_order
❌ read_customer_order → openid missing
❌ openid mall.read_order → cannot mix with administrator Scopes
❌ openid mall.write_product → cannot mix with administrator Scopes
| Rule | Details |
|---|---|
openid required | Without it, the OIDC flow is not used |
| No mixing with administrator Scopes | mall.read_* / mall.write_* Scopes cannot be used together |
Calling a Customer API endpoint with a token that lacks the openid Scope returns 403 Forbidden (insufficient_scope).
🔄 Token Refresh and Revoke
Uses the same endpoints and method as the Admin API.
Refresh
A Refresh Token is valid for 2 weeks. On exchange, a new Refresh Token is issued and the previous one is invalidated.
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}'
| Error Code | HTTP | Cause |
|---|---|---|
invalid_client | 401 | client_id/client_secret is missing or invalid |
invalid_request | 400 | refresh_token is missing |
unsupported_grant_type | 400 | grant_type is not refresh_token |
invalid_grant | 400 | refresh_token is invalid or expired (including a previous value that was already exchanged) → re-authenticate from Step 1 |
Revoke
The Refresh Token linked to the requested token is revoked as well. If token_hint is omitted, the token is treated as an 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'
| Parameter | Required | Description |
|---|---|---|
token | ✅ | Token to revoke |
token_hint | Optional | access_token · refresh_token · id_token |
On success, the server responds with 200 OK and no body. Requests with an invalid token also return 200.
See OAuth 2.0 Authentication Guide — Token Refresh for details.
🌐 OIDC Discovery / Public Keys (JWKS)
Standard OIDC metadata and the public keys for signature verification are provided. These endpoints can be called without authentication.
Discovery Document
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"]
}
Public Keys (JWKS)
Verify the signature with the key whose kid matches the one in the id_token header. Public keys are rotated periodically, and both the current and next keys are provided together, so implement key lookup by kid and make sure your cache can be refreshed.
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" }
]
}
🐛 Error Handling
| Status | Error | Cause | Solution |
|---|---|---|---|
| 400 | invalid_request — code_challenge missing | Client is subject to PKCE but the parameter was not sent | Include code_challenge in Step 1 |
| 400 | invalid_request — code_challenge_method | A value other than S256 was used | Specify S256 |
| 400 | invalid_request — code_verifier missing | Parameter was not sent during token exchange | Include code_verifier in Step 2 |
| 400 | invalid_request — code_verifier format error | Outside the 43 to 128 character range, or contains disallowed characters | Generate 43 to 128 characters using the allowed set (A-Z a-z 0-9 - . _ ~) |
| 400 | Authentication request blocked | Authentication attempted from a WebView | Switch to the system browser |
| 401 | invalid_client | Authorization: Basic header missing or app information invalid | Check the Client ID/Secret in the Developer Center |
| 403 | insufficient_scope | Customer API called without the openid Scope | Add openid to the Scope and re-authenticate |
| — | id_token not issued | Authenticated with an operator account, or openid Scope missing | Re-authenticate with a member account including openid |
See the API Status Code Guide for other common status codes.
📚 Related Documents
- Customer API Reference — Request and response for each endpoint
- OAuth 2.0 Authentication Guide — Admin/Front API authentication
- API Quota Guide — Call limits and handling 429
- API Status Code Guide — All status codes