การรักษาความปลอดภัย API เป็นหัวใจสำคัญของระบบที่ทันสมัย โดยเฉพาะในยุคที่ AI กำลังขับเคลื่อนทุกอุตสาหกรรม บทความนี้จะพาคุณเจาะลึกการ implement OAuth2 authentication กับ API gateway ตั้งแต่พื้นฐานจนถึง production-ready implementation พร้อมโค้ดที่พร้อมใช้งานจริง เหมาะสำหรับนักพัฒนาที่ต้องการสร้างระบบ Authentication ที่แข็งแกร่งและปลอดภัย
ทำไมต้องใช้ OAuth2 กับ API Gateway
ในโลกของ microservices และ distributed systems ยุคปัจจุบัน API gateway ทำหน้าที่เป็น "ประตูหลัก" ในการควบคุมการเข้าถึง services ทั้งหมด การใช้ OAuth2 ช่วยให้คุณสามารถ:
- Centralized Authentication - จัดการ authentication จากจุดเดียว ลดความซับซ้อนของโค้ด
- Token-Based Security - ใช้ access token แทนการส่ง credentials ตรงๆ
- Fine-Grained Access Control - ควบคุมสิทธิ์การเข้าถึงได้ละเอียด
- Scalability - รองรับการขยายระบบได้ง่ายโดยไม่กระทบ security
- AI Integration Ready - เชื่อมต่อกับ AI services ได้อย่างปลอดภัย
OAuth2 Flow พื้นฐานที่ต้องเข้าใจ
ก่อนจะเข้าสู่ implementation มาทำความเข้าใจ OAuth2 flows หลักที่ใช้กันบ่อยในระบบ API:
1. Client Credentials Flow (Machine to Machine)
เหมาะสำหรับ server-to-server communication เช่น microservice ที่ต้องเรียกใช้ service อื่น ไม่มี user interaction
2. Authorization Code Flow
เหมาะสำหรับ web applications ที่ต้องการ user authentication ผ่าน browser
3. Refresh Token Flow
ใช้สำหรับขอ access token ใหม่เมื่อ token เดิมหมดอายุ โดยไม่ต้องให้ user login ใหม่
Implementation ด้วย Node.js และ Express
มาถึงส่วนสำคัญ เราจะ implement API gateway authentication ด้วย OAuth2 กันแบบ step-by-step
การสร้าง OAuth2 Server พื้นฐาน
// oauth2-server.js
const express = require('express');
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
// In-memory storage (ใช้ database จริงใน production)
const clients = new Map();
const tokens = new Map();
const refreshTokens = new Map();
// ตั้งค่า configuration
const config = {
issuer: 'https://api.holysheep.ai',
accessTokenExpiry: 3600, // 1 ชั่วโมง
refreshTokenExpiry: 86400 * 7, // 7 วัน
jwtSecret: process.env.JWT_SECRET || 'your-super-secret-key'
};
// Mock database - clients (applications)
const mockClients = [
{
client_id: 'ecommerce-app',
client_secret: 'ecommerce-secret-123',
redirect_uris: ['http://localhost:3000/callback'],
grants: ['client_credentials', 'authorization_code', 'refresh_token']
},
{
client_id: 'ai-service',
client_secret: 'ai-service-secret-456',
grants: ['client_credentials']
}
];
// Mock users database
const mockUsers = [
{
id: 'user-001',
email: '[email protected]',
password_hash: '$2a$10$X/...', // hashed
role: 'admin'
},
{
id: 'user-002',
email: '[email protected]',
password_hash: '$2a$10$Y/...',
role: 'user'
}
];
// Helper: Generate tokens
function generateTokens(clientId, userId = null, scope = 'read') {
const accessToken = jwt.sign(
{
iss: config.issuer,
sub: userId || clientId,
client_id: clientId,
scope: scope,
type: userId ? 'user' : 'client'
},
config.jwtSecret,
{ expiresIn: config.accessTokenExpiry }
);
const refreshToken = crypto.randomBytes(64).toString('hex');
const refreshTokenExpiry = Math.floor(Date.now() / 1000) + config.refreshTokenExpiry;
// Store refresh token
refreshTokens.set(refreshToken, {
client_id: clientId,
user_id: userId,
scope: scope,
expiry: refreshTokenExpiry
});
return {
access_token: accessToken,
token_type: 'Bearer',
expires_in: config.accessTokenExpiry,
refresh_token: refreshToken
};
}
// POST /oauth/token - Token endpoint
app.post('/oauth/token', (req, res) => {
const { grant_type, client_id, client_secret, refresh_token, username, password } = req.body;
try {
let tokenResponse;
switch (grant_type) {
case 'client_credentials':
// Validate client
const client = mockClients.find(c =>
c.client_id === client_id && c.client_secret === client_secret
);
if (!client) {
return res.status(401).json({
error: 'invalid_client',
error_description: 'Client authentication failed'
});
}
if (!client.grants.includes('client_credentials')) {
return res.status(400).json({
error: 'unsupported_grant_type',
error_description: 'Client does not support this grant type'
});
}
tokenResponse = generateTokens(clientId, null, 'read write');
break;
case 'refresh_token':
// Validate refresh token
if (!refresh_token) {
return res.status(400).json({
error: 'invalid_request',
error_description: 'Refresh token is required'
});
}
const storedToken = refreshTokens.get(refresh_token);
if (!storedToken || storedToken.expiry < Math.floor(Date.now() / 1000)) {
return res.status(401).json({
error: 'invalid_grant',
error_description: 'Refresh token is invalid or expired'
});
}
// Verify client ownership
if (storedToken.client_id !== client_id) {
return res.status(401).json({
error: 'invalid_grant',
error_description: 'Token was not issued to this client'
});
}
tokenResponse = generateTokens(
storedToken.client_id,
storedToken.user_id,
storedToken.scope
);
// Invalidate old refresh token (rotation)
refreshTokens.delete(refresh_token);
break;
case 'password':
// Resource Owner Password Credentials Grant
// ใช้ในกรณีที่ trust สูง เช่น first-party apps
const user = mockUsers.find(u => u.email === username);
if (!user) {
return res.status(401).json({
error: 'invalid_grant',
error_description: 'Invalid credentials'
});
}
// ใน production ใช้ bcrypt.compare
tokenResponse = generateTokens(clientId, user.id, 'read write');
break;
default:
return res.status(400).json({
error: 'unsupported_grant_type',
error_description: Grant type '${grant_type}' is not supported
});
}
res.json(tokenResponse);
} catch (error) {
console.error('Token generation error:', error);
res.status(500).json({
error: 'server_error',
error_description: 'Internal server error'
});
}
});
// GET /oauth/authorize - Authorization endpoint
app.get('/oauth/authorize', (req, res) => {
const { client_id, redirect_uri, response_type, state } = req.query;
// Validate request
const client = mockClients.find(c => c.client_id === client_id);
if (!client || !client.redirect_uris.includes(redirect_uri)) {
return res.status(400).json({ error: 'Invalid client or redirect_uri' });
}
if (response_type !== 'code') {
return res.status(400).json({ error: 'Only code response_type is supported' });
}
// In real app, show login page and consent screen
// For demo, we'll simulate authorization
const authCode = crypto.randomBytes(32).toString('hex');
// Store temporary code (expires in 10 minutes)
tokens.set(authCode, {
client_id,
redirect_uri,
type: 'authorization_code',
expiry: Math.floor(Date.now() / 1000) + 600
});
res.redirect(${redirect_uri}?code=${authCode}&state=${state});
});
// POST /oauth/token (exchange code for token)
app.post('/oauth/token/exchange', (req, res) => {
const { code, client_id, client_secret, redirect_uri } = req.body;
const stored = tokens.get(code);
if (!stored || stored.expiry < Math.floor(Date.now() / 1000)) {
return res.status(400).json({ error: 'Invalid or expired authorization code' });
}
if (stored.client_id !== client_id || stored.redirect_uri !== redirect_uri) {
return res.status(400).json({ error: 'Code was not issued to this client' });
}
tokens.delete(code);
const tokenResponse = generateTokens(client_id, 'user-001', 'read write');
res.json(tokenResponse);
});
app.listen(3000, () => {
console.log('OAuth2 Server running on http://localhost:3000');
});
การสร้าง API Gateway Middleware สำหรับ Token Validation
// api-gateway.js
const express = require('express');
const axios = require('axios');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
const config = {
jwtSecret: process.env.JWT_SECRET || 'your-super-secret-key',
oauthServerUrl: 'http://localhost:3000',
apiBaseUrl: 'https://api.holysheep.ai/v1' // HolySheep API endpoint
};
// Token validation middleware
function validateToken(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: 'missing_token',
message: 'Authorization header with Bearer token is required'
});
}
const token = authHeader.substring(7);
try {
// Verify JWT token
const decoded = jwt.verify(token, config.jwtSecret);
// Attach user/client info to request
req.auth = {
sub: decoded.sub,
clientId: decoded.client_id,
scope: decoded.scope.split(' '),
type: decoded.type
};
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'token_expired',
message: 'Access token has expired. Please use refresh token to get new token.'
});
}
return res.status(401).json({
error: 'invalid_token',
message: 'Token validation failed'
});
}
}
// Scope checking middleware factory
function requireScope(requiredScope) {
return (req, res, next) => {
const userScope = req.auth?.scope || [];
if (!userScope.includes(requiredScope)) {
return res.status(403).json({
error: 'insufficient_scope',
message: Required scope '${requiredScope}' not found,
required: requiredScope,
current: userScope
});
}
next();
};
}
// Rate limiting (simple in-memory implementation)
const rateLimits = new Map();
const RATE_LIMIT_WINDOW = 60000; // 1 minute
const RATE_LIMIT_MAX = 100; // requests per window
function rateLimiter(req, res, next) {
const clientId = req.auth?.clientId || req.ip;
const now = Date.now();
let clientData = rateLimits.get(clientId);
if (!clientData || now - clientData.windowStart > RATE_LIMIT_WINDOW) {
clientData = { count: 0, windowStart: now };
rateLimits.set(clientId, clientData);
}
clientData.count++;
if (clientData.count > RATE_LIMIT_MAX) {
return res.status(429).json({
error: 'rate_limit_exceeded',
message: 'Too many requests. Please try again later.',
retryAfter: Math.ceil((RATE_LIMIT_WINDOW - (now - clientData.windowStart)) / 1000)
});
}
res.setHeader('X-RateLimit-Limit', RATE_LIMIT_MAX);
res.setHeader('X-RateLimit-Remaining', RATE_LIMIT_MAX - clientData.count);
next();
}
// AI Proxy endpoint - ส่งต่อ request ไปยัง HolySheep API
app.post('/ai/chat', validateToken, rateLimiter, requireScope('read'), async (req, res) => {
try {
const { model, messages, temperature, max_tokens } = req.body;
const response = await axios.post(
${config.apiBaseUrl}/chat/completions,
{
model: model || 'gpt-4.1',
messages: messages,
temperature: temperature || 0.7,
max_tokens: max_tokens || 1000
},
{
headers: {
'Authorization': Bearer ${process.env.HOLYSHEEP_API_KEY},
'Content-Type': 'application/json'
},
timeout: 30000
}
);
res.json({
success: true,
data: response.data,
usage: {
client: req.auth.clientId,
timestamp: new Date().toISOString()
}
});
} catch (error) {
console.error('AI Proxy Error:', error.message);
if (error.response) {
return res.status(error.response.status).json({
error: 'upstream_error',
message: error.response.data?.error?.message || 'AI service error'
});
}
res.status(502).json({
error: 'bad_gateway',
message: 'Failed to connect to AI service'
});
}
});
// RAG endpoint with OAuth2 protection
app.post('/ai/rag/search', validateToken, rateLimiter, requireScope('read write'), async (req, res) => {
try {
const { query, top_k, collection } = req.body;
// Forward to RAG service
const ragResponse = await axios.post(
'http://rag-service:8000/search',
{
query,
top_k: top_k || 5,
collection: collection || 'default'
},
{
headers: {
'X-Client-Id': req.auth.clientId,
'X-Request-Id': req.headers['x-request-id']
}
}
);
res.json({
success: true,
results: ragResponse.data.results,
metadata: {
client: req.auth.clientId,
processingTime: ragResponse.headers['x-processing-time']
}
});
} catch (error) {
res.status(500).json({
error: 'rag_search_failed',
message: 'RAG search service error'
});
}
});
// Protected resource example
app.get('/api/products', validateToken, rateLimiter, (req, res) => {
res.json({
success: true,
data: [
{ id: 1, name: 'Product A', price: 299 },
{ id: 2, name: 'Product B', price: 499 }
],
metadata: {
authenticatedClient: req.auth.clientId,
scopes: req.auth.scope
}
});
});
// Admin-only endpoint
app.delete('/api/admin/users/:id', validateToken, rateLimiter, requireScope('admin'), (req, res) => {
res.json({
success: true,
message: User ${req.params.id} deleted by ${req.auth.clientId}
});
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error('Gateway Error:', err);
res.status(500).json({
error: 'internal_error',
message: 'An unexpected error occurred'
});
});
app.listen(8080, () => {
console.log('API Gateway running on http://localhost:8080');
console.log('HolySheep API endpoint:', config.apiBaseUrl);
});
Client Implementation - ตัวอย่างการใช้งาน
// client-example.js - ตัวอย่าง client ที่ใช้ OAuth2
const axios = require('axios');
class OAuth2Client {
constructor(config) {
this.clientId = config.clientId;
this.clientSecret = config.clientSecret;
this.tokenEndpoint = config.tokenEndpoint || 'http://localhost:3000/oauth/token';
this.apiGatewayUrl = config.apiGatewayUrl || 'http://localhost:8080';
this.accessToken = null;
this.refreshToken = null;
this.tokenExpiry = null;
}
// Client Credentials Flow - สำหรับ M2M
async getTokenClientCredentials(scope = 'read') {
try {
const response = await axios.post(this.tokenEndpoint,
new URLSearchParams({
grant_type: 'client_credentials',
client_id: this.clientId,
client_secret: this.clientSecret,
scope: scope
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
this.accessToken = response.data.access_token;
this.refreshToken = response.data.refresh_token;
this.tokenExpiry = Date.now() + (response.data.expires_in * 1000);
return response.data;
} catch (error) {
throw new Error(Token request failed: ${error.response?.data?.error_description || error.message});
}
}
// Refresh token - ขอ token ใหม่
async refreshAccessToken() {
if (!this.refreshToken) {
throw new Error('No refresh token available');
}
try {
const response = await axios.post(this.tokenEndpoint,
new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: this.refreshToken,
client_id: this.clientId,
client_secret: this.clientSecret
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
this.accessToken = response.data.access_token;
this.refreshToken = response.data.refresh_token;
this.tokenExpiry = Date.now() + (response.data.expires_in * 1000);
console.log('Token refreshed successfully');
return response.data;
} catch (error) {
// Refresh token หมดอายุ ต้อง login ใหม่
if (error.response?.data?.error === 'invalid_grant') {
console.log('Refresh token expired. Please re-authenticate.');
this.refreshToken = null;
}
throw error;
}
}
// Check if token needs refresh (refresh 5 minutes before expiry)
async ensureValidToken() {
if (!this.accessToken || Date.now() >= this.tokenExpiry - 300000) {
await this.refreshAccessToken();
}
}
// Make authenticated request
async request(method, path, data = null) {
await this.ensureValidToken();
const config = {
method,
url: ${this.apiGatewayUrl}${path},
headers: {
'Authorization': Bearer ${this.accessToken},
'Content-Type': 'application/json'
}
};
if (data) {
config.data = data;
}
try {
const response = await axios(config);
return response.data;
} catch (error) {
// Handle 401 with automatic retry
if (error.response?.status === 401) {
await this.refreshAccessToken();
config.headers['Authorization'] = Bearer ${this.accessToken};
const retryResponse = await axios(config);
return retryResponse.data;
}
throw error;
}
}
// Convenience methods
get(path) { return this.request('GET', path); }
post(path, data) { return this.request('POST', path, data); }
put(path, data) { return this.request('PUT', path, data); }
delete(path) { return this.request('DELETE', path); }
}
// ตัวอย่างการใช้งาน
async function demo() {
const client = new OAuth2Client({
clientId: 'ecommerce-app',
clientSecret: 'ecommerce-secret-123',
tokenEndpoint: 'http://localhost:3000/oauth/token',
apiGatewayUrl: 'http://localhost:8080'
});
try {
// 1. Get token using client credentials
console.log('Getting access token...');
const tokenData = await client.getTokenClientCredentials('read write');
console.log('Token received:', tokenData.access_token.substring(0, 20) + '...');
// 2. Get protected products
console.log('\nFetching products...');
const products = await client.get('/api/products');
console.log('Products:', JSON.stringify(products, null, 2));
// 3. Send AI chat request
console.log('\nSending AI chat request...');
const aiResponse = await client.post('/ai/chat', {
model: 'gpt-4.1',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'What is the capital of Thailand?' }
],
temperature: 0.7
});
console.log('AI Response:', JSON.stringify(aiResponse, null, 2));
// 4. Test rate limiting
console.log('\nTesting rate limit headers...');
const response = await axios.get('http://localhost:8080/api/products', {
headers: { 'Authorization': Bearer ${client.accessToken} }
});
console.log('Rate Limit Headers:', {
limit: response.headers['x-ratelimit-limit'],
remaining: response.headers['x-ratelimit-remaining']
});
} catch (error) {
console.error('Error:', error.message);
if (error.response) {
console.error('Response data:', error.response.data);
}
}
}
// Run demo
demo();
Best Practices สำหรับ Production
เมื่อนำไปใช้งานจริง ควรคำนึงถึง best practices เหล่านี้:
- ใช้ HTTPS เสมอ - ไม่ว่าจะเป็น token endpoint หรือ API endpoints
- เก็บ secrets ใน environment variables - ไม่ hardcode ลงในโค้ด
- ใช้ database ที่เหมาะสม - Redis สำหรับ token storage, PostgreSQL สำหรับ users
- Implement token revocation - ให้ user สามารถ logout และ revoke token ได้
- Log ทุกการ authentication - เพื่อ audit และตรวจจับความผิดปกติ
- ใช้ short-lived access tokens - ลดความเสี่ยงหาก token รั่วไหล
- Rate limiting ที่เหมาะสม - ป้องกัน brute force attacks
การเชื่อมต่อกับ HolySheep AI API
สำหรับการใช้งาน AI services ผ่าน API gateway ที่ปลอดภัย คุณสามารถใช้ สมัครที่นี่ เพื่อรับ API key จาก HolySheep AI ซึ่งมีความโดดเด่นเรื่อง:
- ความเร็ว <50ms - Latency ต่ำมาก เหมาะสำหรับ real-time applications
- ราคาประหยัด 85%+ - อัตรา ¥1=$1 คุ้มค่าสำหรับ production
- รองรับหลาย models - GPT-4.1, Claude Sonnet 4.5, Gemini 2.5 Flash, DeepSeek V3.2
- ชำระเงินง่าย - รองรับ WeChat และ Alipay
ตัวอย่างการใช้งาน HolySheep กับ OAuth2
// holySheep-integration.js
const axios = require('axios');
class HolySheepAIClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.holysheep.ai/v1';
}
async chatCompletion(messages, options = {}) {
const response = await axios.post(
${this.baseUrl}/chat/completions,
{
model: options.model || 'gpt-4.1',
messages: messages,
temperature: options.temperature ?? 0.7,
max_tokens: options.max_tokens || 1000,
stream: options.stream || false
},
{
headers: {
'Authorization': Bearer ${this.apiKey},
'Content-Type': 'application/json'
},
timeout: options.timeout || 30000
}
);
return response.data;
}
async embeddings(text, model = 'text-embedding-3-small') {
const response = await axios.post(
${this.baseUrl}/embeddings,
{
input: text,
model: model
},
{
headers: {
'Authorization': Bearer ${this.apiKey},
'Content-Type': 'application/json'
}
}
);
return response.data;
}
async imageGeneration(prompt, options = {}) {
const response = await axios.post(
${this.baseUrl}/images/generations,
{
prompt: prompt,
model: options.model || 'dall-e-3',
n: options.n || 1,
quality: options.quality || 'standard',
size: options.size || '1024x1024'
},
{
headers: {
'Authorization': Bearer ${this.apiKey},
'Content-Type': 'application/json'
}
}
);
return response.data;
}
}
// ตัวอย่างการใช้งานใน API Gateway
async function protectedAIEndpoint(req, res) {
const { clientId, scope } = req.auth;
// ตรวจสอบว่า client มีสิทธิ์ใช้ AI หรือไม่
if (!scope.includes('ai') && !scope.includes('read')) {
return res.status(403).json({
error: 'access_denied',
message: 'Client does not have AI access permissions'
});
}
const holySheepClient = new HolySheepAIClient(process.env.HOLYSHEEP_API_KEY);
try {
const result = await holySheepClient.chatCompletion(req.body.messages, {
model: req.body.model,
temperature: req.body.temperature
});
// Log usage สำหรับ billing
await logUsage(clientId, 'chat', result.usage);
res.json({
success: true,
data: result,
billing: {
prompt_tokens: result.usage.prompt_tokens,
completion_tokens: result.usage.completion_tokens,
total_tokens: result.usage.total_tokens
}
});
} catch (error) {
console.error('HolySheep API Error:', error.message);
res.status(error.response?.status || 500).json({
error: 'ai_service_error',
message: error.message
});
}
}
// Mock usage logging
async function logUsage(clientId, endpoint, usage) {
console.log([BILLING] Client: ${clientId}, Endpoint: ${endpoint}, Usage:, usage);
}
module.exports = { HolySheepAIClient, protectedAIEndpoint };
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
1. Error: "invalid_token" - Token ไม่ถูกต้องหรือหมดอายุ
// ❌ วิธีที่ผิด - ไม่จัดก