OAuth 2.0 Authentication Guide
CAFE24 API provides an OAuth 2.0-based secure authentication system. This guide explains step-by-step authentication methods and token management in detail.
📚 Table of Contents
- OAuth 2.0 Overview
- Authentication Flow
- Step 1: Request Authorization Code
- Step 2: Issue Access Token
- Step 3: API Call
- Token Refresh
- Token Revoke
- Authentication Methods by API
- Security Best Practices
- Error Handling and Troubleshooting
- Production Checklist
🔑 OAuth 2.0 Overview
OAuth 2.0 is an open standard that allows third-party applications to securely access APIs without directly sharing user credentials.
Key Features
- Security: Does not expose user passwords
- Permission Control: Fine-grained permission management via Scope
- Token-based: Uses temporary Access Token
- Refreshable: Automatic refresh via Refresh Token
Core Terms
| Term | Description |
|---|---|
| Resource Owner | Shopping mall administrator (end user) |
| Client | Application registered in CAFE24 App Store |
| Authorization Server | CAFE24 OAuth server |
| Resource Server | CAFE24 API server |
| Access Token | Temporary token representing API access permission |
| Refresh Token | Token for refreshing Access Token |
| Scope | Permission range held by the token |
| Authorization Code | Temporary authorization code (valid for 1 minute) |
🔄 Authentication Flow

🎯 Step 1: Request Authorization Code
Overview
When a user approves an application, you receive an Authorization Code. This code is required to issue an Access Token.
⚠️ Important: Code requests should ONLY be made in a web browser. Do not request directly using cURL or programming languages.
Required Parameters
| Parameter | Description | Example |
|---|---|---|
mall_id | Shopping mall ID | yourmall |
response_type | Response type (fixed) | code |
client_id | App's Client ID | BrIfqEKoPxeE..... |
redirect_uri | Redirect URL after authentication | https://yourapp.com/callback |
scope | Permission scope to request (space-separated) | mall.read_product mall.read_order |
state | CSRF prevention token (required) | random_string_12345 |
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={scope}
Actual Usage Example
# Enter in web browser address bar or provide as a link
https://yourmall.cafe24api.com/api/v2/oauth/authorize?response_type=code&client_id=BrIfqEKoPxeE&state=xyz789&redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback&scope=mall.read_product+mall.read_order+mall.read_store
Success Response
When the user approves permissions, CAFE24 server redirects to the configured redirect_uri:
HTTP/1.1 302 Found
Location: https://yourapp.com/callback?code={authorization_code}&state=xyz789
Response Parameters
| Parameter | Description |
|---|---|
code | Authorization Code (valid for 1 minute, single use) |
state | State value sent with request (for CSRF verification) |
Implementation Examples
Node.js/Express
const express = require('express');
const crypto = require('crypto');
const app = express();
const CLIENT_ID = process.env.CAFE24_CLIENT_ID;
const CLIENT_SECRET = process.env.CAFE24_CLIENT_SECRET;
const MALL_ID = process.env.CAFE24_MALL_ID;
const REDIRECT_URI = process.env.CAFE24_REDIRECT_URI;
// When login button is clicked
app.get('/login', (req, res) => {
// Generate State value (CSRF prevention)
const state = crypto.randomBytes(16).toString('hex');
// Save to session
req.session.oauthState = state;
// Define Scope
const scopes = ['mall.read_product', 'mall.read_order', 'mall.read_store'].join(' ');
// Create CAFE24 OAuth URL
const authUrl = new URL(`https://${MALL_ID}.cafe24api.com/api/v2/oauth/authorize`);
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('client_id', CLIENT_ID);
authUrl.searchParams.append('state', state);
authUrl.searchParams.append('redirect_uri', REDIRECT_URI);
authUrl.searchParams.append('scope', scopes);
// Redirect user to OAuth page
res.redirect(authUrl.toString());
});
Python/Flask
from flask import Flask, session, redirect, url_for, request
from urllib.parse import urlencode
import secrets
app = Flask(__name__)
CLIENT_ID = os.getenv('CAFE24_CLIENT_ID')
CLIENT_SECRET = os.getenv('CAFE24_CLIENT_SECRET')
MALL_ID = os.getenv('CAFE24_MALL_ID')
REDIRECT_URI = os.getenv('CAFE24_REDIRECT_URI')
@app.route('/login')
def login():
# Generate State value
state = secrets.token_urlsafe(32)
session['oauth_state'] = state
session.modified = True
# Define Scope
scopes = ['mall.read_product', 'mall.read_order', 'mall.read_store']
# Configure parameters
params = {
'response_type': 'code',
'client_id': CLIENT_ID,
'state': state,
'redirect_uri': REDIRECT_URI,
'scope': ' '.join(scopes)
}
# Create OAuth URL
auth_url = f"https://{MALL_ID}.cafe24api.com/api/v2/oauth/authorize?{urlencode(params)}"
return redirect(auth_url)
🎯 Step 2: Issue Access Token
Overview
Use the Authorization Code to receive an Access Token required for actual API calls. This request must be made securely from the backend server.
Required Parameters
| Parameter | Description | Example |
|---|---|---|
grant_type | Request type (fixed) | authorization_code |
code | Authorization Code received from Step 1 | abc123... |
redirect_uri | Same Redirect URL used in Step 1 | https://yourapp.com/callback |
Authorization Header
Authorization: Basic {base64_encode({client_id}:{client_secret})}
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}'
Base64 Encoding Generation
# macOS/Linux
echo -n "BrIfqEKoPxeE:xYz789" | base64
# Result: QnJJZnFFSm9QeGVFOnh5Wig0OQ==
Success Response (200 OK)
{
"access_token": "0iqR5nM5EJIq..........",
"expires_at": "2021-03-01T14:00:00.000",
"refresh_token": "JeTJ7XpnFC0P..........",
"refresh_token_expires_at": "2021-03-15T12:00:00.000",
"client_id": "BrIfqEKoPxeE..........",
"mall_id": "yourmall",
"user_id": "test",
"scopes": [
"mall.read_order",
"mall.read_product",
"mall.read_store"
],
"issued_at": "2021-03-01T12:00:00.000",
"shop_no": "1",
"token_type": "Bearer"
}
Response Field Description
| Field | Description | Validity Period |
|---|---|---|
access_token | Token for API calls | 2 hours |
refresh_token | Token for refreshing tokens | 2 weeks |
expires_at | Access Token expiration time | - |
refresh_token_expires_at | Refresh Token expiration time | - |
token_type | Token type (always Bearer) | - |
scopes | List of approved permissions | - |
issued_at | Token issuance time | - |
Implementation Examples
Node.js/Express
const axios = require('axios');
const Base64 = require('js-base64').Base64;
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// Validate State
if (state !== req.session.oauthState) {
return res.status(401).json({ error: 'Invalid state' });
}
try {
// Base64 encoding
const auth = Base64.encode(`${CLIENT_ID}:${CLIENT_SECRET}`);
// Token request
const response = await axios.post(
`https://${MALL_ID}.cafe24api.com/api/v2/oauth/token`,
`grant_type=authorization_code&code=${code}&redirect_uri=${REDIRECT_URI}`,
{
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
const tokenData = response.data;
// Save tokens to DB or session
req.session.tokens = {
access_token: tokenData.access_token,
refresh_token: tokenData.refresh_token,
expires_at: new Date(tokenData.expires_at),
refresh_token_expires_at: new Date(tokenData.refresh_token_expires_at)
};
// Save user information
req.session.user = {
mall_id: tokenData.mall_id,
user_id: tokenData.user_id,
shop_no: tokenData.shop_no,
scopes: tokenData.scopes
};
res.redirect('/dashboard');
} catch (error) {
console.error('Token exchange error:', error.response?.data);
res.status(400).json({ error: 'Failed to get access token' });
}
});
Python/Flask
import requests
import base64
from datetime import datetime
@app.route('/callback')
def callback():
code = request.args.get('code')
state = request.args.get('state')
# Validate State
if state != session.get('oauth_state'):
return jsonify({'error': 'Invalid state'}), 401
try:
# Base64 encoding
auth_string = f"{CLIENT_ID}:{CLIENT_SECRET}"
auth_bytes = auth_string.encode('utf-8')
auth_b64 = base64.b64encode(auth_bytes).decode('utf-8')
# Token request
response = requests.post(
f"https://{MALL_ID}.cafe24api.com/api/v2/oauth/token",
data={
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': REDIRECT_URI
},
headers={
'Authorization': f'Basic {auth_b64}',
'Content-Type': 'application/x-www-form-urlencoded'
}
)
token_data = response.json()
# Save tokens
session['tokens'] = {
'access_token': token_data['access_token'],
'refresh_token': token_data['refresh_token'],
'expires_at': token_data['expires_at'],
'refresh_token_expires_at': token_data['refresh_token_expires_at']
}
session['user'] = {
'mall_id': token_data['mall_id'],
'user_id': token_data['user_id'],
'shop_no': token_data['shop_no'],
'scopes': token_data['scopes']
}
session.modified = True
return redirect('/dashboard')
except Exception as e:
print(f"Token exchange error: {e}")
return jsonify({'error': 'Failed to get access token'}), 400
🎯 Step 3: API Call
Overview
Use the issued Access Token to call CAFE24 APIs.
Authorization Header
Include the Access Token in Bearer token format in all API requests:
Authorization: Bearer {access_token}
Request Examples
Retrieve Products
curl -X GET \\
'https://yourmall.cafe24api.com/api/v2/admin/products' \\
-H 'Authorization: Bearer 0iqR5nM5EJIq..........' \\
-H 'Content-Type: application/json'
Create Order
curl -X POST \\
'https://yourmall.cafe24api.com/api/v2/admin/orders' \\
-H 'Authorization: Bearer 0iqR5nM5EJIq..........' \\
-H 'Content-Type: application/json' \\
-d '{
"customer_name": "John Doe",
"customer_email": "john@example.com",
"items": [
{
"product_no": 128,
"quantity": 2
}
]
}'
Implementation Examples
Node.js/Express
async function makeApiRequest(endpoint, method = 'GET', data = null) {
const { access_token } = req.session.tokens;
const config = {
method,
url: `https://${MALL_ID}.cafe24api.com/api/v2/admin${endpoint}`,
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json'
}
};
if (data) {
config.data = data;
}
try {
const response = await axios(config);
return response.data;
} catch (error) {
if (error.response?.status === 401) {
// Token expired, refresh required
throw new Error('Token expired');
}
throw error;
}
}
// Usage example
app.get('/api/products', async (req, res) => {
try {
const products = await makeApiRequest('/products');
res.json(products);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
🔄 Token Refresh
Overview
Access Token expires after 2 hours. You can use the Refresh Token to issue a new Access Token.
Important Notes
- Refresh Token validity period: 2 weeks
- Automatic refresh: Requesting before Refresh Token expiration returns a new Refresh Token as well
- Previous token revocation: Previous Refresh Token is automatically revoked when a new token is issued
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=refresh_token&refresh_token={refresh_token}'
Success Response (200 OK)
{
"access_token": "21EZes0dGSfN..........",
"expires_at": "2021-03-01T15:50:00.000",
"refresh_token": "xLlhWztQHBik............",
"refresh_token_expires_at": "2021-03-15T13:50:00.000",
"client_id": "BrIfqEKoPxeE..........",
"mall_id": "yourmall",
"user_id": "test",
"scopes": ["mall.read_order", "mall.read_product", "mall.read_store"],
"issued_at": "2021-03-01T13:50:00.000",
"shop_no": "1",
"token_type": "Bearer"
}
Implementation Example - Auto Refresh Logic
Node.js/Express - Middleware
const Base64 = require('js-base64').Base64;
// Token refresh function
async function refreshAccessToken(refreshToken) {
try {
const auth = Base64.encode(`${CLIENT_ID}:${CLIENT_SECRET}`);
const response = await axios.post(
`https://${MALL_ID}.cafe24api.com/api/v2/oauth/token`,
`grant_type=refresh_token&refresh_token=${refreshToken}`,
{
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
return response.data;
} catch (error) {
console.error('Token refresh error:', error.response?.data);
throw error;
}
}
// Middleware - Auto token refresh
app.use(async (req, res, next) => {
// Skip if no tokens
if (!req.session.tokens) {
return next();
}
const { access_token, refresh_token, expires_at } = req.session.tokens;
const now = new Date();
const expiresDate = new Date(expires_at);
// Refresh 5 minutes before token expiration
const refreshThreshold = new Date(expiresDate.getTime() - 5 * 60 * 1000);
if (now > refreshThreshold) {
try {
const newTokens = await refreshAccessToken(refresh_token);
req.session.tokens = {
access_token: newTokens.access_token,
refresh_token: newTokens.refresh_token,
expires_at: new Date(newTokens.expires_at),
refresh_token_expires_at: new Date(newTokens.refresh_token_expires_at)
};
console.log('Token refreshed successfully');
} catch (error) {
console.error('Token refresh failed, logging out user');
req.session.destroy();
return res.redirect('/login');
}
}
next();
});
Python/Flask
def refresh_access_token(refresh_token):
"""Issue new Access Token with Refresh Token"""
try:
auth_string = f"{CLIENT_ID}:{CLIENT_SECRET}"
auth_bytes = auth_string.encode('utf-8')
auth_b64 = base64.b64encode(auth_bytes).decode('utf-8')
response = requests.post(
f"https://{MALL_ID}.cafe24api.com/api/v2/oauth/token",
data={
'grant_type': 'refresh_token',
'refresh_token': refresh_token
},
headers={
'Authorization': f'Basic {auth_b64}',
'Content-Type': 'application/x-www-form-urlencoded'
}
)
return response.json()
except Exception as e:
print(f"Token refresh error: {e}")
raise
# Flask before_request - Auto token refresh
@app.before_request
def check_and_refresh_token():
if 'tokens' not in session:
return
try:
expires_at = datetime.fromisoformat(session['tokens']['expires_at'].replace('Z', '+00:00'))
refresh_threshold = expires_at - timedelta(minutes=5)
if datetime.now(timezone.utc) > refresh_threshold or datetime.now() > expires_at.replace(tzinfo=None):
new_tokens = refresh_access_token(session['tokens']['refresh_token'])
session['tokens'] = {
'access_token': new_tokens['access_token'],
'refresh_token': new_tokens['refresh_token'],
'expires_at': new_tokens['expires_at'],
'refresh_token_expires_at': new_tokens['refresh_token_expires_at']
}
session.modified = True
print('Token refreshed successfully')
except Exception as e:
print(f'Token refresh failed: {e}')
session.clear()
return redirect('/login')
🛑 Token Revoke
Overview
You can explicitly revoke tokens when a user logs out or API access is no longer needed.
Important Notes
- Revoking an Access Token also revokes the associated Refresh Token
- API calls with revoked tokens result in 401 Unauthorized error
Request
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={token_hint}'
Parameters
| Parameter | Description | Example |
|---|---|---|
token | Token to revoke (Access or Refresh) | 0iqR5nM5EJIq.. |
token_hint | Token type | access_token or refresh_token |
Implementation Examples
Node.js/Express
async function revokeToken(token, tokenHint = 'access_token') {
try {
const auth = Base64.encode(`${CLIENT_ID}:${CLIENT_SECRET}`);
await axios.post(
`https://${MALL_ID}.cafe24api.com/api/v2/oauth/revoke`,
`token=${token}&token_hint=${tokenHint}`,
{
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
console.log(`${tokenHint} revoked successfully`);
} catch (error) {
console.error('Token revoke error:', error.response?.data);
throw error;
}
}
// Logout endpoint
app.get('/logout', async (req, res) => {
try {
if (req.session.tokens) {
// Revoke Access Token
await revokeToken(req.session.tokens.access_token, 'access_token');
}
req.session.destroy();
res.redirect('/login');
} catch (error) {
console.error('Logout error:', error);
// Remove session even if token revocation fails
req.session.destroy();
res.redirect('/login');
}
});
📡 Authentication Methods by API
Admin API (OAuth 2.0)
Authentication Method: Bearer Token
curl -X GET \\
'https://yourmall.cafe24api.com/api/v2/admin/products' \\
-H 'Authorization: Bearer {access_token}' \\
-H 'Content-Type: application/json'
Requirements:
- OAuth 2.0 authentication required
- Access Token required
Front API
The Front API retrieves public information such as products and categories, and does not require OAuth 2.0 authentication. Two methods are supported, and call limits differ depending on whether the request is authenticated.
Method 1. Basic Authentication (Recommended)
Join client_id and front_api_key with a colon (:), encode the result in Base64, and place it in the Authorization header.
curl -X GET \\
'https://yourmall.cafe24api.com/api/v2/products' \\
-H 'Authorization: Basic {base64_encode({client_id}:{front_api_key})}' \\
-H 'Content-Type: application/json'
The front_api_key is issued from the Developer Center.
Generating the Base64 value
# macOS/Linux
echo -n "BrIfqEKoPxeE:frontApiKey123" | base64
Implementation Examples
// Node.js
const Base64 = require('js-base64').Base64;
const auth = Base64.encode(`${CLIENT_ID}:${FRONT_API_KEY}`);
const response = await axios.get(
`https://${MALL_ID}.cafe24api.com/api/v2/products`,
{
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json'
}
}
);
# Python
import base64
import requests
auth_string = f"{CLIENT_ID}:{FRONT_API_KEY}"
auth_b64 = base64.b64encode(auth_string.encode('utf-8')).decode('utf-8')
response = requests.get(
f"https://{MALL_ID}.cafe24api.com/api/v2/products",
headers={
'Authorization': f'Basic {auth_b64}',
'Content-Type': 'application/json'
}
)
Method 2. Unauthenticated (Client ID Header)
This method passes only the client_id in a header.
curl -X GET \\
'https://yourmall.cafe24api.com/api/v2/products' \\
-H 'X-Cafe24-Client-Id: {client_id}' \\
-H 'Content-Type: application/json'
Comparison
| Category | Basic Auth (Recommended) | Unauthenticated |
|---|---|---|
| Credentials | client_id + front_api_key | client_id |
| Header | Authorization: Basic ... | X-Cafe24-Client-Id: ... |
| Call limit | Standard limit | Reduced limit |
| Recommended | ✅ For production use | Limited use |
If both headers are sent, the Authorization header takes precedence.
Use Basic authentication for stable service operation. See the API Quota Guide for call limit details.
Protect front_api_key as carefully as client_secret.
If your application calls the API directly from a browser, route the requests through your own server instead.
Cafe24 Analytics API (OAuth 2.0)
Authentication Method: Bearer Token
curl -X GET \\
'https://ca-api.cafe24data.com/visitors/pageview' \\
-H 'Authorization: Bearer {access_token}' \\
-H 'Content-Type: application/json'
🛡️ Security Best Practices
1. Protect Client Secret
// ✅ Use environment variables
const CLIENT_SECRET = process.env.CAFE24_CLIENT_SECRET;
// .env file
CAFE24_CLIENT_ID=BrIfqEKoPxeE
CAFE24_CLIENT_SECRET=xYz789
// Add to .gitignore
echo ".env" >> .gitignore
2. HTTPS Required
// ✅ Always use HTTPS
const redirectUri = 'https://yourapp.com/callback';
3. Prevent CSRF with State Value
const crypto = require('crypto');
const state = crypto.randomBytes(32).toString('hex');
session.oauthState = state;
// Verify in callback
if (req.query.state !== session.oauthState) {
throw new Error('CSRF attack detected');
}
4. Token Storage Methods
Server Session (Recommended)
// Store tokens only in server session
req.session.tokens = {
access_token: token.access_token,
refresh_token: token.refresh_token,
expires_at: token.expires_at
};
Database Storage (Encrypted)
CREATE TABLE user_tokens (
id INT PRIMARY KEY,
user_id VARCHAR(255),
access_token VARCHAR(255) ENCRYPTED,
refresh_token VARCHAR(255) ENCRYPTED,
expires_at DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
5. Handle Token Expiration
// Check token expiration time
const isTokenExpired = (expiresAt) => {
return new Date() > new Date(expiresAt);
};
6. Verify Permissions (Check Scope)
const requiredScopes = ['mall.read_product', 'mall.write_order'];
const grantedScopes = tokens.scopes;
const hasAllScopes = requiredScopes.every(scope =>
grantedScopes.includes(scope)
);
if (!hasAllScopes) {
throw new Error('Insufficient permissions');
}
🐛 Error Handling and Troubleshooting
401 Unauthorized
Symptoms: "error": "invalid_grant" or "error": "invalid_token"
Causes:
- Authorization Code expired (must be used within 1 minute)
- Attempt to reuse Authorization Code
- Incorrect Client ID/Secret
- Token expired
Solutions:
// Attempt token refresh
if (error.response?.status === 401) {
try {
const newTokens = await refreshAccessToken(refresh_token);
// Retry with new token
} catch (refreshError) {
// Refresh failed, re-login required
res.redirect('/login');
}
}
400 Bad Request
Symptoms: "error": "invalid_request"
Causes:
- Missing required parameters
- Incorrect redirect_uri
- Content-Type error
Solutions:
// Verify all required parameters
const requiredParams = ['grant_type', 'code', 'redirect_uri'];
const hasAllParams = requiredParams.every(param => data[param]);
if (!hasAllParams) {
throw new Error('Missing required parameters');
}
403 Forbidden
Symptoms: Insufficient Scope permissions
Causes:
- Requested Scope was not approved
- User denied specific permissions during consent
Solutions:
// Check requested Scope
const requiredScopes = ['mall.read_product', 'mall.write_order'];
const missingScopes = requiredScopes.filter(
scope => !tokens.scopes.includes(scope)
);
if (missingScopes.length > 0) {
console.log(`Missing scopes: ${missingScopes.join(', ')}`);
// Re-authentication required
res.redirect('/login');
}
✅ Production Checklist
Development Phase
- OAuth flow tested in local development environment
- Redirect URI tested with localhost
- State value validation implemented
- Token storage mechanism implemented
- Error handling logic implemented
Security Review
- Client Secret stored only in environment variables
- .env file included in .gitignore
- HTTPS applied
- Tokens stored only in server session
- CSRF token (State) validation implemented
- Token expiration handling implemented
API Testing
- Authorization Code issuance tested
- Access Token issuance tested
- API call tested (all Scopes)
- Token refresh tested
- Token revoke tested
- Scope permission validation tested
Pre-Deployment
- Redirect URI changed to production URL
- Redirect URI registration verified in Developer Center
- Environment variable configuration verified
- HTTPS certificate verified
- Error logging configured
- Token refresh logic tested
Monitoring
- Token expiration issues monitored
- API call failures logged
- Unauthorized errors tracked
- Token refresh success/failure rate monitored
📚 Additional Resources
🤝 Support
If you encounter issues:
- Check the Troubleshooting section of this guide
- API Documentation