การรักษาความปลอดภัย API เป็นหัวใจสำคัญของระบบที่ทันสมัย โดยเฉพาะในยุคที่ AI กำลังขับเคลื่อนทุกอุตสาหกรรม บทความนี้จะพาคุณเจาะลึกการ implement OAuth2 authentication กับ API gateway ตั้งแต่พื้นฐานจนถึง production-ready implementation พร้อมโค้ดที่พร้อมใช้งานจริง เหมาะสำหรับนักพัฒนาที่ต้องการสร้างระบบ Authentication ที่แข็งแกร่งและปลอดภัย

ทำไมต้องใช้ OAuth2 กับ API Gateway

ในโลกของ microservices และ distributed systems ยุคปัจจุบัน API gateway ทำหน้าที่เป็น "ประตูหลัก" ในการควบคุมการเข้าถึง services ทั้งหมด การใช้ OAuth2 ช่วยให้คุณสามารถ:

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 เหล่านี้:

การเชื่อมต่อกับ HolySheep AI API

สำหรับการใช้งาน AI services ผ่าน API gateway ที่ปลอดภัย คุณสามารถใช้ สมัครที่นี่ เพื่อรับ API key จาก HolySheep AI ซึ่งมีความโดดเด่นเรื่อง:

ตัวอย่างการใช้งาน 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 ไม่ถูกต้องหรือหมดอายุ

// ❌ วิธีที่ผิด - ไม่จัดก