การนำ REST API ไปใช้งานจริงไม่ได้มีเพียงการสร้าง Endpoint ให้ Client เรียกใช้งานเท่านั้น แต่ยังต้องจัดการเรื่อง Authentication, Security, Routing, Rate Limiting, Logging และการควบคุมการเข้าถึง API ด้วย

แนวทางหนึ่งที่นิยมคือการวาง API Gateway ไว้ด้านหน้า Backend API เพื่อให้ Gateway เป็นจุดกลางในการรับ Request ก่อนส่งต่อไปยัง Service ภายในระบบ

บทความนี้เป็น Workshop แบบลงมือทำ โดยจะเริ่มตั้งแต่

  • สร้าง REST API ด้วย Node.js + Express
  • Dockerize REST API
  • ติดตั้ง Kong Gateway และ PostgreSQL ด้วย Docker Compose
  • สร้าง Kong Service
  • สร้าง Public Route และ Protected Route
  • สร้าง Kong Consumer
  • สร้าง JWT Credential
  • เปิดใช้งาน JWT Plugin
  • ขอ JWT จาก Authentication Endpoint
  • ส่ง JWT ผ่าน Authorization: Bearer
  • ทดสอบกรณี Token ถูกต้อง/ไม่ถูกต้อง
  • ตรวจสอบ Header ที่ Kong ส่งต่อให้ Backend

หมายเหตุเรื่องเวอร์ชัน: Workshop นี้ใช้ kong:3.9.3 ซึ่งเป็น Kong OSS Docker Official Image ที่เผยแพร่อยู่ในชุด OSS ณ วันที่จัดทำบทความ (30 กันยายน 2026) เพื่อให้สามารถทดลองได้โดยไม่ต้องใช้ Kong Enterprise license


#1. Kong API Gateway คืออะไร

Kong Gateway ทำหน้าที่เป็น Reverse Proxy/API Gateway อยู่ระหว่าง Client และ Backend API

Client
   |
   v
Kong API Gateway
   |
   +-- Authentication
   +-- Routing
   +-- Rate Limiting
   +-- Logging
   +-- Security Plugins
   |
   v
Backend API

แทนที่ Client จะเรียก Backend โดยตรง เช่น

http://backend:3000/products

Client จะเรียกผ่าน Kong เช่น

http://localhost:8000/api/products

จากนั้น Kong จะตรวจสอบ Request และส่งต่อไปยัง Backend


#2. JWT Authentication คืออะไร

JWT หรือ JSON Web Token เป็น Token ที่ประกอบด้วย 3 ส่วน

HEADER.PAYLOAD.SIGNATURE

ตัวอย่าง Payload

{
  "iss": "demo-client",
  "sub": "student",
  "role": "user",
  "iat": 1790740000,
  "exp": 1790743600
}

Claim ที่สำคัญใน Workshop นี้คือ

Claim ความหมาย
iss ผู้ออก Token หรือ Key ที่ Kong ใช้ค้นหา Credential
sub Subject หรือผู้ใช้ที่ Token แทน
iat เวลาที่ Token ถูกสร้าง
exp เวลาหมดอายุ

Kong JWT Plugin จะตรวจสอบ Signature ของ JWT ก่อนอนุญาตให้ Request ผ่านไปยัง Backend API

เอกสาร Kong ระบุว่า JWT Plugin รองรับการตรวจ JWT ที่ลงนามด้วยอัลกอริทึม เช่น HS256 และ RS256 และสามารถตรวจ registered claims อย่าง exp และ nbf ได้


#3. สถาปัตยกรรมของ Workshop

เราจะสร้างระบบดังนี้

flowchart LR
    C["Client / Postman"]

    subgraph KONG["Kong API Gateway :8000"]
        AUTH["Public Route<br/>/auth"]
        APIROUTE["Protected Route<br/>/api"]
        JWT["JWT Plugin"]
        CON["Consumer<br/>student-client"]
    end

    APP["Node.js REST API<br/>api:3000"]
    DB["PostgreSQL<br/>Kong Database"]

    C -->|"POST /auth/token"| AUTH
    AUTH --> APP

    C -->|"GET /api/products<br/>Bearer JWT"| APIROUTE
    APIROUTE --> JWT
    JWT --> CON
    JWT -->|"Valid"| APP

    KONG --- DB

เราจะแยก Route เป็นสองกลุ่ม

Route ตัวอย่าง JWT
Public /auth/token ไม่ต้องใช้
Protected /api/products ต้องใช้

#4. JWT Plugin ไม่ได้ทำหน้าที่ Login

จุดที่มักเข้าใจผิดคือ Kong JWT Plugin ไม่ได้เป็น Login Server

JWT Plugin มีหน้าที่หลักคือ

รับ JWT
   |
   v
อ่าน Claim
   |
   v
ค้นหา Consumer Credential
   |
   v
ตรวจ Signature
   |
   v
ตรวจ exp / nbf ตาม Configuration
   |
   +---- Invalid ---> 401
   |
   +---- Valid -----> Backend API

ดังนั้นเรายังต้องมีระบบที่ทำหน้าที่ออก JWT

ใน Workshop นี้ เราจะสร้าง Endpoint

POST /token

ใน Node.js API เพื่อจำลอง Authentication Service


#5. สิ่งที่ต้องติดตั้ง

เครื่องที่ใช้ Workshop ควรมี

  • Docker
  • Docker Compose
  • cURL หรือ Postman
  • Text Editor เช่น Visual Studio Code

ตรวจสอบ Docker

docker --version

ตรวจสอบ Docker Compose

docker compose version

#6. สร้าง Project

สร้างโฟลเดอร์

mkdir kong-jwt-workshop
cd kong-jwt-workshop

สร้างโฟลเดอร์ API

mkdir api

โครงสร้าง Project จะเป็น

kong-jwt-workshop/
├── docker-compose.yml
└── api/
    ├── Dockerfile
    ├── package.json
    └── app.js

#7. สร้าง REST API ด้วย Node.js + Express

สร้างไฟล์

api/package.json
{
  "name": "kong-jwt-demo-api",
  "version": "1.0.0",
  "description": "REST API for Kong JWT workshop",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^5.1.0",
    "jsonwebtoken": "^9.0.2"
  }
}

#8. สร้าง REST API

สร้างไฟล์

api/app.js
const express = require("express");
const jwt = require("jsonwebtoken");

const app = express();

app.use(express.json());

const PORT = process.env.PORT || 3000;

const DEMO_USERNAME =
  process.env.DEMO_USERNAME || "student";

const DEMO_PASSWORD =
  process.env.DEMO_PASSWORD || "1234";

const JWT_KEY =
  process.env.JWT_KEY || "demo-client";

const JWT_SECRET =
  process.env.JWT_SECRET || "super-secret-change-me";


// ---------------------------------------------------
// Public endpoint
// ---------------------------------------------------

app.get("/", (req, res) => {

  res.json({
    message: "Kong JWT Workshop API",
    status: "running"
  });

});


// ---------------------------------------------------
// Login / Token endpoint
// ---------------------------------------------------

app.post("/token", (req, res) => {

  const {
    username,
    password
  } = req.body;


  if (
    username !== DEMO_USERNAME ||
    password !== DEMO_PASSWORD
  ) {

    return res.status(401).json({
      message: "Invalid username or password"
    });

  }


  const token = jwt.sign(

    {
      iss: JWT_KEY,
      sub: username,
      role: "user"
    },

    JWT_SECRET,

    {
      algorithm: "HS256",
      expiresIn: "1h"
    }

  );


  res.json({

    access_token: token,

    token_type: "Bearer",

    expires_in: 3600

  });

});


// ---------------------------------------------------
// Protected API
// ---------------------------------------------------

app.get("/products", (req, res) => {

  res.json({

    message: "Protected products API",

    kong_consumer: {

      id:
        req.headers["x-consumer-id"] || null,

      username:
        req.headers["x-consumer-username"] || null,

      custom_id:
        req.headers["x-consumer-custom-id"] || null,

      credential:
        req.headers["x-credential-identifier"] || null

    },

    data: [

      {
        id: 1,
        name: "Mechanical Keyboard",
        price: 2490
      },

      {
        id: 2,
        name: "Wireless Mouse",
        price: 1290
      },

      {
        id: 3,
        name: "USB-C Hub",
        price: 1590
      }

    ]

  });

});


app.get("/profile", (req, res) => {

  res.json({

    message: "Protected profile API",

    consumer:
      req.headers["x-consumer-username"] || null

  });

});


app.listen(
  PORT,
  "0.0.0.0",
  () => {

    console.log(
      `API running on port ${PORT}`
    );

  }
);

#9. ความสัมพันธ์ระหว่าง iss กับ Kong JWT Credential

ใน Code มี

{
  iss: JWT_KEY
}

และกำหนด

JWT_KEY=demo-client

ดังนั้น JWT Payload จะมี

{
  "iss": "demo-client"
}

ภายหลังเราจะสร้าง JWT Credential ใน Kong โดยกำหนด

key = demo-client

จึงต้องจำความสัมพันธ์นี้ไว้

JWT Payload

iss = demo-client

       ==

Kong JWT Credential

key = demo-client

#10. สร้าง Dockerfile

สร้างไฟล์

api/Dockerfile
FROM node:22-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install --omit=dev

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

#11. สร้าง Docker Compose

กลับมาที่ Root ของ Project

kong-jwt-workshop/

สร้างไฟล์

docker-compose.yml
services:

  kong-db:
    image: postgres:17-alpine
    container_name: kong-db

    restart: unless-stopped

    environment:
      POSTGRES_DB: kong
      POSTGRES_USER: kong
      POSTGRES_PASSWORD: kong

    volumes:
      - kong_db_data:/var/lib/postgresql/data

    healthcheck:
      test:
        [
          "CMD-SHELL",
          "pg_isready -U kong -d kong"
        ]

      interval: 5s
      timeout: 5s
      retries: 10

    networks:
      - kong-net


  kong-migrations:
    image: kong:3.9.3
    container_name: kong-migrations

    command: kong migrations bootstrap

    environment:

      KONG_DATABASE: postgres

      KONG_PG_HOST: kong-db

      KONG_PG_DATABASE: kong

      KONG_PG_USER: kong

      KONG_PG_PASSWORD: kong

    depends_on:

      kong-db:
        condition: service_healthy

    networks:
      - kong-net

    restart: "no"


  kong:
    image: kong:3.9.3
    container_name: kong

    restart: unless-stopped

    environment:

      KONG_DATABASE: postgres

      KONG_PG_HOST: kong-db

      KONG_PG_DATABASE: kong

      KONG_PG_USER: kong

      KONG_PG_PASSWORD: kong

      KONG_PROXY_LISTEN:
        0.0.0.0:8000

      KONG_ADMIN_LISTEN:
        0.0.0.0:8001

    depends_on:

      kong-migrations:
        condition: service_completed_successfully

    ports:

      - "8000:8000"

      - "8001:8001"

    networks:
      - kong-net


  api:

    build:
      context: ./api

    container_name: demo-api

    restart: unless-stopped

    environment:

      PORT: 3000

      DEMO_USERNAME: student

      DEMO_PASSWORD: "1234"

      JWT_KEY: demo-client

      JWT_SECRET: super-secret-change-me

    networks:
      - kong-net


volumes:

  kong_db_data:


networks:

  kong-net:

    driver: bridge

#12. Port ที่ใช้ใน Workshop

Port หน้าที่
8000 Kong Proxy
8001 Kong Admin API
3000 Backend API ภายใน Docker Network
5432 PostgreSQL ภายใน Docker Network

Client จะเรียก API ผ่าน

http://localhost:8000

ไม่ใช่

http://localhost:3000

เพราะเราไม่ได้ Publish Port ของ Backend API ออกมาที่ Host

นี่เป็นการบังคับให้ Request ผ่าน API Gateway


#13. Start ระบบ

รัน

docker compose up -d --build

ตรวจสอบ Container

docker compose ps

ควรพบอย่างน้อย

kong-db
kong
demo-api

ส่วน

kong-migrations

จะทำงานเพียงครั้งเดียวเพื่อสร้าง Database Schema แล้วจบการทำงาน


#14. ตรวจสอบ Log

ดู Kong Log

docker compose logs -f kong

ดู API Log

docker compose logs -f api

#15. ตรวจสอบ Kong Admin API

เรียก

curl http://localhost:8001

ถ้าระบบทำงานถูกต้อง Kong จะตอบข้อมูล Gateway กลับมา

Port 8001 เป็น Admin API จึงไม่ควรเปิดสู่ Public Internet ในระบบ Production


#16. ทดสอบ Backend API จากภายใน Docker Network

สามารถทดสอบจาก Kong Container ได้ เช่น

docker exec kong \
  curl http://api:3000

ถ้าสำเร็จควรได้

{
  "message": "Kong JWT Workshop API",
  "status": "running"
}

ชื่อ

api

มาจาก Service Name ใน Docker Compose

Docker DNS จะ Resolve ชื่อนี้ไปยัง Container ของ API


#17. สร้าง Kong Service

Kong Service ใช้แทน Backend หรือ Upstream API

เราจะสร้าง Service ชื่อ

demo-api

รัน

curl -i \
  -X POST \
  http://localhost:8001/services \
  --data name=demo-api \
  --data url=http://api:3000

ตรวจสอบ

curl \
  http://localhost:8001/services/demo-api

Architecture ตอนนี้คือ

Kong

  |
  v

Service: demo-api

  |
  v

http://api:3000

#18. สร้าง Public Route

สร้าง Route สำหรับ Authentication

curl -i \
  -X POST \
  http://localhost:8001/services/demo-api/routes \
  --data name=auth-route \
  --data 'paths[]=/auth' \
  --data strip_path=true \
  --data 'protocols[]=http' \
  --data 'protocols[]=https'

เมื่อ Client เรียก

POST /auth/token

Kong จะตัด /auth ออก

เพราะ

strip_path=true

จึงส่ง Request ไป Backend เป็น

POST /token

Flow

POST http://localhost:8000/auth/token

                |
                v

             Kong

                |
        strip /auth
                |
                v

       http://api:3000/token

#19. สร้าง Protected Route

สร้าง Route สำหรับ Business API

curl -i \
  -X POST \
  http://localhost:8001/services/demo-api/routes \
  --data name=api-route \
  --data 'paths[]=/api' \
  --data strip_path=true \
  --data 'protocols[]=http' \
  --data 'protocols[]=https'

ดังนั้น

GET /api/products

จะถูกส่งไปยัง

GET /products

ที่ Backend


#20. ตรวจสอบ Route

curl \
  http://localhost:8001/routes

หรือดูเฉพาะ Route ของ Service

curl \
  http://localhost:8001/services/demo-api/routes

#21. ทดสอบ Login ก่อนเปิด JWT Plugin

เรียก

curl \
  -X POST \
  http://localhost:8000/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "username": "student",
    "password": "1234"
  }'

ตัวอย่าง Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}

#22. ทดสอบ Products ก่อนเปิด JWT

curl \
  http://localhost:8000/api/products

ขณะนี้ยังสามารถเรียกได้ เพราะเรายังไม่ได้เปิด JWT Plugin


#23. Kong Consumer คืออะไร

Consumer เป็น Entity ที่ Kong ใช้แทน Client หรือ Application ที่ได้รับอนุญาตให้เรียก API

ตัวอย่าง

Web Frontend
Mobile Application
Partner Application
Internal Service
IoT Device

Workshop นี้สร้าง Consumer ชื่อ

student-client

#24. สร้าง Consumer

curl -i \
  -X POST \
  http://localhost:8001/consumers \
  --data username=student-client \
  --data custom_id=student-app

ตรวจสอบ

curl \
  http://localhost:8001/consumers/student-client

#25. สร้าง JWT Credential

สร้าง JWT Credential ให้ Consumer

curl -i \
  -X POST \
  http://localhost:8001/consumers/student-client/jwt \
  --data algorithm=HS256 \
  --data key=demo-client \
  --data secret=super-secret-change-me

ค่าใน Workshop

algorithm = HS256

key = demo-client

secret = super-secret-change-me

ตรวจสอบ

curl \
  http://localhost:8001/consumers/student-client/jwt

#26. ทำไม key ต้องตรงกับ iss

JWT ที่ Node.js สร้างมี

{
  "iss": "demo-client"
}

Credential ของ Kong มี

key = demo-client

Kong จะใช้ Claim ที่กำหนดใน

config.key_claim_name

เพื่อค้นหา Credential

Workshop นี้จะใช้

config.key_claim_name = iss

ดังนั้น Flow คือ

JWT

iss = demo-client

       |
       v

Kong JWT Plugin

       |
       v

ค้นหา Credential

key = demo-client

#27. เปิด JWT Plugin ให้ api-route

เราจะเปิด JWT เฉพาะ

api-route

เพื่อให้

/auth/token

ยังเป็น Public Endpoint

รัน

curl -i \
  -X POST \
  http://localhost:8001/routes/api-route/plugins \
  -H "Content-Type: application/json" \
  -d '{
    "name": "jwt",
    "config": {
      "key_claim_name": "iss",
      "header_names": [
        "authorization"
      ],
      "claims_to_verify": [
        "exp"
      ]
    }
  }'

Configuration สำคัญคือ

key_claim_name = iss

และ

claims_to_verify = exp

หมายความว่า Kong จะตรวจ Expiration ของ Token ด้วย


#28. ตรวจสอบ JWT Plugin

curl \
  http://localhost:8001/routes/api-route/plugins

ควรพบ Plugin

jwt

#29. ทดสอบ Protected API โดยไม่ส่ง JWT

ลองเรียก

curl -i \
  http://localhost:8000/api/products

ควรได้รับสถานะ

401 Unauthorized

Request จะถูกหยุดที่ Kong

Client

   |
   v

Kong JWT Plugin

   |
   +---- ไม่มี JWT
   |
   v

401 Unauthorized

Backend API ไม่จำเป็นต้องจัดการ Request นี้เอง


#30. ขอ JWT Token

curl \
  -X POST \
  http://localhost:8000/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "username": "student",
    "password": "1234"
  }'

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Copy ค่า access_token


#31. เรียก Protected API ด้วย Bearer Token

Linux / macOS / Git Bash

TOKEN="วาง-token-ตรงนี้"

จากนั้นเรียก

curl -i \
  http://localhost:8000/api/products \
  -H "Authorization: Bearer $TOKEN"

ถ้า Token ถูกต้องจะได้

HTTP/1.1 200 OK

#32. ตัวอย่าง Response

{
  "message": "Protected products API",
  "kong_consumer": {
    "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "username": "student-client",
    "custom_id": "student-app",
    "credential": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  },
  "data": [
    {
      "id": 1,
      "name": "Mechanical Keyboard",
      "price": 2490
    },
    {
      "id": 2,
      "name": "Wireless Mouse",
      "price": 1290
    },
    {
      "id": 3,
      "name": "USB-C Hub",
      "price": 1590
    }
  ]
}

#33. Kong ส่ง Consumer Header ไปยัง Backend

เมื่อ Authentication สำเร็จ Kong สามารถเพิ่ม Header เกี่ยวกับ Consumer ให้ Upstream

ตัวอย่าง Header

X-Consumer-ID
X-Consumer-Username
X-Consumer-Custom-ID
X-Credential-Identifier

Backend จึงอ่านได้ เช่น

const consumer =
  req.headers["x-consumer-username"];

แนวทางนี้มีประโยชน์เมื่อ Backend ต้องการทราบว่า Request ผ่าน Credential ของ Consumer ใด


#34. ทดสอบ /profile

เรียก

curl \
  http://localhost:8000/api/profile \
  -H "Authorization: Bearer $TOKEN"

ตัวอย่าง Response

{
  "message": "Protected profile API",
  "consumer": "student-client"
}

#35. ทดสอบ Token ไม่ถูกต้อง

curl -i \
  http://localhost:8000/api/products \
  -H "Authorization: Bearer invalid-token"

Request ควรถูก Reject

Client
   |
   v
Kong
   |
   v
JWT Plugin
   |
   +--- Invalid JWT
   |
   v
401

#36. ทดสอบ Token ที่ถูกแก้ Payload

JWT ประกอบด้วย

Header.Payload.Signature

แม้ JWT Payload จะอ่านได้ แต่หากแก้ Payload โดยไม่สร้าง Signature ใหม่ที่ถูกต้อง

Kong จะตรวจ Signature ไม่ผ่าน

นี่คือเหตุผลที่ JWT ไม่ได้มีเป้าหมายเพื่อ ซ่อนข้อมูล

แต่มีเป้าหมายหลักเพื่อทำให้ตรวจสอบได้ว่า Token ถูกแก้ไขหรือไม่ และถูก Sign ด้วย Key ที่เชื่อถือได้หรือไม่

หลีกเลี่ยงการใส่ Password หรือ Secret ลงใน JWT Payload


#37. ทดสอบบน Windows PowerShell

สร้าง Request Body

$body = @{
    username = "student"
    password = "1234"
} | ConvertTo-Json

Login

$response = Invoke-RestMethod `
    -Method Post `
    -Uri "http://localhost:8000/auth/token" `
    -ContentType "application/json" `
    -Body $body

เก็บ Token

$token = $response.access_token

ดู Token

$token

เรียก Protected API

Invoke-RestMethod `
    -Method Get `
    -Uri "http://localhost:8000/api/products" `
    -Headers @{
        Authorization = "Bearer $token"
    }

#38. ทดสอบด้วย Postman

#Request 1: Login

Method

POST

URL

http://localhost:8000/auth/token

Body → Raw → JSON

{
  "username": "student",
  "password": "1234"
}

#Request 2: Products

Method

GET

URL

http://localhost:8000/api/products

เลือก

Authorization

Type

Bearer Token

แล้วใส่ Token ที่ได้จาก Login


#39. Postman Tests เพื่อเก็บ Token อัตโนมัติ

ใน Request /auth/token สามารถเพิ่ม Script หลัง Response เพื่อเก็บ Token เป็น Environment Variable

ตัวอย่าง

const json = pm.response.json();

pm.environment.set(
  "access_token",
  json.access_token
);

จากนั้น Request /api/products ใช้

{{access_token}}

ใน Bearer Token


#40. ดู Services ทั้งหมด

curl \
  http://localhost:8001/services

#41. ดู Routes ทั้งหมด

curl \
  http://localhost:8001/routes

#42. ดู Consumers ทั้งหมด

curl \
  http://localhost:8001/consumers

#43. ดู JWT Credentials

curl \
  http://localhost:8001/consumers/student-client/jwt

#44. ดู Plugins

curl \
  http://localhost:8001/plugins

#45. Plugin สามารถติดตั้งระดับใดได้บ้าง

โดยทั่วไป Kong Plugin สามารถผูกกับ Context ที่เหมาะสม เช่น Global, Service หรือ Route

Workshop นี้เลือก Route-level

api-route

เพราะต้องการผลลัพธ์ดังนี้

/auth/*
    |
    +--- Public


/api/*
    |
    +--- JWT Required

ถ้าเราเปิด JWT ที่ demo-api Service โดยตรง ทั้ง /auth และ /api อาจถูกครอบด้วย Plugin ใน Scope ของ Service ตามการตั้งค่าที่ใช้ ทำให้ Login Endpoint ถูกป้องกันด้วย JWT ไปด้วย ซึ่งขัดกับ Flow ที่ต้องการใน Workshop นี้


#46. Sequence Diagram

sequenceDiagram

    participant C as Client
    participant K as Kong Gateway
    participant A as Node.js API
    participant D as Kong DB

    C->>K: POST /auth/token
    K->>A: POST /token

    A-->>K: JWT
    K-->>C: JWT

    C->>K: GET /api/products + Bearer JWT

    K->>D: Find JWT credential from iss
    D-->>K: Consumer credential

    K->>K: Verify JWT signature
    K->>K: Verify exp

    alt Token valid

        K->>A: GET /products
        Note over K,A: X-Consumer-* headers

        A-->>K: Products JSON
        K-->>C: 200 OK

    else Token invalid

        K-->>C: 401 Unauthorized

    end

#47. Error: 404 no Route matched with those values

หากเรียก API แล้วพบประมาณ

no Route matched with those values

ตรวจสอบ Route

curl \
  http://localhost:8001/routes

ตรวจสอบว่า Path ตรงกับที่เรียกหรือไม่

ตัวอย่าง Route

/auth
/api

ต้องเรียกผ่าน Proxy Port

8000

ไม่ใช่ Admin API Port

8001

#48. Error: 502 Bad Gateway

มักเกิดจาก Kong เชื่อมต่อ Backend ไม่ได้

ตรวจ Container

docker compose ps

ดู API Log

docker compose logs api

ทดสอบจาก Kong Container

docker exec kong \
  curl http://api:3000

ถ้าเรียกไม่ได้ ให้ตรวจ

  • ชื่อ Docker Compose Service
  • Docker Network
  • API Port
  • API Process
  • Container Status

#49. Error: JWT ถูกสร้างแล้วแต่ยัง 401

ตรวจค่าต่อไปนี้

JWT Payload

iss = demo-client

Kong Credential

key = demo-client

Node.js

JWT_SECRET=super-secret-change-me

Kong

secret=super-secret-change-me

ความสัมพันธ์ต้องเป็น

JWT iss
   ==
Kong key

และ

JWT signing secret
   ==
Kong credential secret

#50. Error: Token หมดอายุ

เราเปิด

claims_to_verify = exp

ดังนั้น Kong จะ Reject Token ที่หมดอายุ

ให้ Login ใหม่

POST /auth/token

เพื่อรับ Token ใหม่


#51. Reset Workshop

หยุดระบบ

docker compose down

หากต้องการลบ Kong Database ด้วย

docker compose down -v

จากนั้นสร้างใหม่

docker compose up -d --build

docker compose down -v จะลบ Named Volume ของ PostgreSQL ใน Workshop นี้ด้วย


#52. ทำไม Workshop นี้ใช้ HS256

HS256 เข้าใจง่ายและเหมาะสำหรับอธิบายพื้นฐาน

Authentication API

       |
       | Shared Secret
       v

     Sign JWT


Kong Gateway

       |
       | Same Shared Secret
       v

    Verify JWT

ข้อจำกัดคือระบบที่ Verify Token ต้องมี Shared Secret


#53. Production ควรพิจารณา RS256

แนวทาง Asymmetric Key

Authentication Server

        |
        | Private Key
        v

      Sign JWT


Kong Gateway

        |
        | Public Key
        v

      Verify JWT

ข้อดีคือ Kong ไม่จำเป็นต้องถือ Private Key สำหรับ Sign Token

Kong JWT Plugin รองรับการตรวจ JWT ที่ลงนามด้วย Public/Private-key algorithms ที่รองรับตาม Configuration ของ Plugin


#54. JWT กับ OAuth 2.0 / OpenID Connect ไม่ใช่สิ่งเดียวกัน

JWT เป็น Token Format

OAuth 2.0 เป็น Authorization Framework

OpenID Connect เป็น Identity Layer บน OAuth 2.0

ดังนั้นระบบจริงอาจมี Architecture เช่น

User

 |
 v

Identity Provider
(Keycloak / Entra ID / Auth0 / etc.)

 |
 | Access Token
 v

Kong Gateway

 |
 | Verify / Authenticate
 v

Backend Services

สำหรับระบบที่ต้องการ Login, Refresh Token, SSO, MFA, User Federation และ Key Rotation ควรพิจารณา Identity Provider แทนการสร้าง Authentication System แบบง่ายเอง


#55. Security Checklist สำหรับ Production

ตัวอย่างแนวทางที่ควรพิจารณา

  • ใช้ HTTPS
  • ไม่ Hard-code JWT Secret
  • เก็บ Secret ใน Secret Manager
  • ตั้ง Token Expiration
  • ทำ Key Rotation
  • จำกัด Network ของ Kong Admin API
  • ไม่เปิด Port 8001 ให้ Internet
  • ใช้ Strong Password
  • อย่าใส่ข้อมูลลับใน JWT Payload
  • เพิ่ม Rate Limiting
  • เพิ่ม Logging / Observability
  • พิจารณา ACL หรือ Authorization เพิ่มเติม
  • แยก Authentication Service จาก Business Service
  • ใช้ RS256/ES256 หรือ Identity Provider เมื่อเหมาะสม
  • ใช้ Short-lived Access Token
  • ออกแบบ Refresh Token อย่างปลอดภัย หากระบบต้องใช้
  • ตรวจสอบ CORS ตาม Frontend ที่อนุญาต
  • ใช้ Secret ที่มี Entropy สูง
  • ไม่ใช้ Username/Password ตัวอย่างจาก Workshop ใน Production

#56. Workshop Challenge

หลังจากทำ Workshop หลักเสร็จ ลองทำโจทย์ต่อไปนี้

#Challenge 1: Orders API

เพิ่ม Endpoint

POST /orders

และให้เข้าถึงผ่าน

POST /api/orders

#Challenge 2: Consumer เพิ่มเติม

สร้าง

mobile-client

และ

partner-client

ให้แต่ละ Consumer มี JWT Credential ของตนเอง


#Challenge 3: Rate Limiting

ติดตั้ง Rate Limiting Plugin ให้ Protected API

แนวคิด

/api/*
     |
     +--- JWT
     |
     +--- Rate Limiting

#Challenge 4: Consumer Groups / ACL

แบ่ง Client เป็น

user
admin
partner

และออกแบบ Route ที่แต่ละกลุ่มเข้าถึงได้


#Challenge 5: เปลี่ยนจาก HS256 เป็น RS256

ทดลองใช้

Private Key
Public Key

แทน Shared Secret


#Challenge 6: ใช้ Identity Provider

เปลี่ยน Login Endpoint แบบ Demo ไปใช้ Identity Provider เช่น

  • Keycloak
  • Microsoft Entra ID
  • Auth0

แล้วออกแบบให้ Gateway ตรวจ Token จาก Identity Provider


#57. สรุปองค์ประกอบสำคัญ

การทำ JWT Authentication ด้วย Kong ใน Workshop นี้มีองค์ประกอบหลัก

Service
Route
Consumer
JWT Credential
JWT Plugin

ความสัมพันธ์

Client

 |
 | Bearer JWT
 v

Kong Route

 |
 v

JWT Plugin

 |
 | iss
 v

Consumer Credential

 |
 | Signature Valid
 v

Kong Service

 |
 v

Backend API

ค่าที่สำคัญมากคือ

JWT payload

iss = demo-client

ต้องตรงกับ

Kong JWT Credential

key = demo-client

และ Shared Secret สำหรับ HS256 ต้องตรงกัน

Node.js JWT_SECRET
         ==
Kong JWT Credential secret

#58. Final Architecture

flowchart TB

    CLIENT["Client / Postman"]

    PROXY["Kong Proxy<br/>localhost:8000"]

    AUTH_ROUTE["auth-route<br/>/auth<br/>Public"]

    API_ROUTE["api-route<br/>/api<br/>Protected"]

    JWT_PLUGIN["JWT Plugin<br/>Verify iss + signature + exp"]

    CONSUMER["Consumer<br/>student-client"]

    CREDENTIAL["JWT Credential<br/>key = demo-client<br/>HS256"]

    SERVICE["Kong Service<br/>demo-api"]

    API["Node.js API<br/>api:3000"]

    CLIENT --> PROXY

    PROXY --> AUTH_ROUTE
    AUTH_ROUTE --> SERVICE

    PROXY --> API_ROUTE
    API_ROUTE --> JWT_PLUGIN

    JWT_PLUGIN --> CONSUMER
    CONSUMER --> CREDENTIAL

    JWT_PLUGIN --> SERVICE

    SERVICE --> API

#59. สรุป

Workshop นี้แสดงให้เห็นการใช้ Kong API Gateway ร่วมกับ JWT Authentication ตั้งแต่ต้นจนจบ

เราได้ทดลองตั้งแต่

สร้าง REST API
        |
        v
Dockerize API
        |
        v
ติดตั้ง Kong
        |
        v
สร้าง Service
        |
        v
สร้าง Routes
        |
        v
สร้าง Consumer
        |
        v
สร้าง JWT Credential
        |
        v
ติดตั้ง JWT Plugin
        |
        v
ออก JWT
        |
        v
เรียก Protected API

แนวทางนี้ช่วยให้ Responsibility ชัดเจนขึ้น

Kong Gateway
    |
    +--- Routing
    +--- JWT Validation
    +--- Gateway Security

Backend API
    |
    +--- Business Logic
    +--- Data Processing

สำหรับ Workshop หรือระบบทดลอง การใช้ HS256 ช่วยให้เข้าใจ Flow ได้ง่าย แต่สำหรับ Production ควรพิจารณาเรื่อง Key Management, HTTPS, Admin API Security, Authorization, Key Rotation และ Identity Provider เพิ่มเติมตามระดับความเสี่ยงของระบบ


#References

  1. Kong Gateway — JWT Plugin
    https://developer.konghq.com/plugins/jwt/

  2. Kong Gateway — Authenticate Consumers with JWT
    https://developer.konghq.com/how-to/authenticate-consumers-jwt/

  3. Kong Gateway — JWT in Authorization Header
    https://developer.konghq.com/plugins/jwt/examples/header/

  4. Kong Gateway — Verify Registered Claims
    https://developer.konghq.com/plugins/jwt/examples/verified-claim/

  5. Kong Gateway — Install using Docker Compose
    https://developer.konghq.com/gateway/install/docker/

  6. Kong Gateway — Consumers
    https://developer.konghq.com/gateway/entities/consumer/

  7. Kong Gateway — Routes
    https://developer.konghq.com/gateway/entities/route/

  8. Kong Gateway — Plugins
    https://developer.konghq.com/gateway/entities/plugin/

  9. Kong Docker Official Image
    https://hub.docker.com/_/kong