Workshop ภาษาไทยสำหรับเรียนรู้การป้องกัน API ด้วย Kong API Gateway JWT Plugin โดยใช้ Consumer, JWT Credential และการตรวจสอบ exp claim


#1. ภาพรวม

JWT (JSON Web Token) เป็นมาตรฐานสำหรับส่งข้อมูลที่ผ่านการลงลายมือชื่อดิจิทัลระหว่างระบบ โดย Kong API Gateway สามารถใช้ JWT Plugin เพื่อตรวจสอบ token ก่อนอนุญาตให้ request ผ่านไปยัง upstream API

ลำดับการทำงานโดยทั่วไปคือ

Client
  |
  |  Authorization: Bearer <JWT>
  v
Kong API Gateway
  |
  |-- ตรวจ Route
  |-- อ่าน JWT
  |-- อ่านค่า iss
  |-- หา JWT Credential ของ Consumer
  |-- ตรวจ Signature
  |-- ตรวจ exp / nbf ตามที่ตั้งค่า
  |
  +---- Invalid ----> 401 Unauthorized
  |
  +---- Valid ------> Upstream API

Kong JWT Plugin รองรับการรับ JWT จาก HTTP header, query string และ cookie โดยค่าเริ่มต้นจะตรวจ header Authorization และใช้ claim iss เป็น key สำหรับระบุ JWT credential

ใน production ควรให้ Auth Service หรือ Identity Provider เป็นผู้ออก JWT ส่วน Kong ทำหน้าที่ตรวจสอบ token และบังคับใช้นโยบายก่อนส่ง request ไปยัง backend


#2. วัตถุประสงค์ของ Workshop

หลังจบ Workshop จะสามารถ

  1. สร้าง Gateway Service และ Route
  2. เปิดใช้งาน JWT Plugin บน Route
  3. สร้าง Consumer
  4. สร้าง JWT Credential แบบ HS256
  5. สร้าง JWT สำหรับทดสอบ
  6. ส่ง JWT ผ่าน Authorization: Bearer
  7. ทดสอบกรณีไม่มี token
  8. ทดสอบ token ถูกต้อง
  9. ทดสอบ token หมดอายุ
  10. เข้าใจแนวทางนำ JWT ไปใช้งานจริง

#3. สิ่งที่ต้องมี

Workshop นี้สมมติว่า Kong Gateway ทำงานอยู่แล้ว โดยใช้

Component URL
Kong Proxy http://localhost:8000
Kong Admin API http://localhost:8001
Upstream https://httpbin.org

ตรวจสอบ Kong Admin API

curl http://localhost:8001

ถ้า Kong ทำงานปกติจะได้ข้อมูล version และ configuration กลับมา


#4. Architecture

flowchart LR
    C[Client] -->|Authorization: Bearer JWT| K[Kong API Gateway]
    K --> R[Route]
    R --> J[JWT Plugin]
    J -->|Read iss| CR[JWT Credential]
    CR --> CO[Consumer]
    J -->|Valid| U[Upstream API]
    J -->|Invalid / Expired| X[401 Unauthorized]

องค์ประกอบหลักมีดังนี้

  • Service แทน upstream API
  • Route กำหนด URL ที่ Client ใช้เรียก
  • Plugin เพิ่มความสามารถให้ Service/Route
  • Consumer แทน client/application/user ที่ Kong รู้จัก
  • JWT Credential เก็บข้อมูลที่ใช้ตรวจสอบ JWT ของ Consumer

#Part 1: สร้าง API บน Kong

#5. สร้าง Gateway Service

สร้าง Service ชื่อ demo-api

curl -i -X POST http://localhost:8001/services \
  --data name=demo-api \
  --data url=https://httpbin.org

ตรวจสอบ Service

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

#6. สร้าง Route

สร้าง Route ชื่อ demo-route

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

เมื่อ strip_path=true

Client:
http://localhost:8000/api/get

Kong ส่งไป:
https://httpbin.org/get

ตรวจสอบ Route

curl http://localhost:8001/routes/demo-route

#7. ทดสอบ API ก่อนเปิด Authentication

curl -i http://localhost:8000/api/get

ควรได้ HTTP status ประมาณ

HTTP/1.1 200 OK

ตอนนี้ API ยังเรียกได้โดยไม่ต้องส่ง JWT


#Part 2: เปิด JWT Authentication

#8. เปิด JWT Plugin บน Route

เราจะเปิด JWT เฉพาะ demo-route

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

ความหมายของ configuration

{
  "header_names": ["authorization"],
  "key_claim_name": "iss",
  "claims_to_verify": ["exp"]
}
Option ความหมาย
header_names Header ที่ Kong ใช้ค้นหา JWT
key_claim_name Claim ที่ใช้ระบุ credential
claims_to_verify Registered claims ที่ต้องการตรวจเพิ่ม

ใน Workshop นี้กำหนดให้ตรวจ exp เพื่อไม่ยอมรับ token ที่หมดอายุ

Kong JWT Plugin รองรับการตรวจ registered claims exp และ nbf


#9. ทดสอบโดยยังไม่ส่ง JWT

curl -i http://localhost:8000/api/get

ควรได้

HTTP/1.1 401 Unauthorized

ตัวอย่าง response

{
  "message": "Unauthorized"
}

แสดงว่า JWT Plugin เริ่มทำงานแล้ว


#Part 3: Consumer และ JWT Credential

#10. สร้าง Consumer

สร้าง Consumer ชื่อ workshop-user

curl -i -X POST http://localhost:8001/consumers \
  --data username=workshop-user

ตรวจสอบ

curl http://localhost:8001/consumers/workshop-user

#11. สร้าง JWT Credential

สำหรับ Workshop จะใช้ HS256

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

ค่าที่สำคัญ

algorithm = HS256
key       = workshop-key
secret    = workshop-super-secret-change-me

#key ไม่ใช่ secret

ใน JWT payload เราจะกำหนด

{
  "iss": "workshop-key"
}

Kong จะนำ iss ไปค้นหา JWT Credential ที่มี

key = workshop-key

จากนั้นจึงใช้ secret ของ credential นั้นตรวจ signature


#12. ตรวจสอบ JWT Credential

curl http://localhost:8001/consumers/workshop-user/jwt

อย่าเปิดเผย JWT secret ใน production และไม่ควร hard-code secret ใน source code


#Part 4: สร้าง JWT

#13. ติดตั้ง PyJWT

python -m pip install PyJWT

ตรวจสอบ

python -c "import jwt; print(jwt.__version__)"

#14. สร้างไฟล์ generate_token.py

import time
import jwt

SECRET = "workshop-super-secret-change-me"

payload = {
    "iss": "workshop-key",
    "sub": "workshop-user",
    "iat": int(time.time()),
    "exp": int(time.time()) + 3600
}

token = jwt.encode(
    payload,
    SECRET,
    algorithm="HS256"
)

print(token)

สร้าง token

python generate_token.py

จะได้ JWT รูปแบบ

xxxxx.yyyyy.zzzzz

JWT ประกอบด้วย

Header.Payload.Signature

ตัวอย่าง payload

{
  "iss": "workshop-key",
  "sub": "workshop-user",
  "iat": 1760000000,
  "exp": 1760003600
}

#Part 5: เรียก API ด้วย JWT

#15. Linux/macOS

เก็บ token ลง environment variable

export JWT_TOKEN=$(python generate_token.py)

เรียก API

curl -i http://localhost:8000/api/get \
  -H "Authorization: Bearer $JWT_TOKEN"

หาก token ถูกต้องควรได้

HTTP/1.1 200 OK

#16. Windows PowerShell

$JWT_TOKEN = python generate_token.py

เรียก API

curl.exe -i http://localhost:8000/api/get `
  -H "Authorization: Bearer $JWT_TOKEN"

#17. Windows CMD

for /f "delims=" %i in ('python generate_token.py') do set JWT_TOKEN=%i

เรียก API

curl -i http://localhost:8000/api/get -H "Authorization: Bearer %JWT_TOKEN%"

ถ้าใช้คำสั่งในไฟล์ .bat ให้เปลี่ยน %i เป็น %%i


#Part 6: ตรวจสอบ Header ที่ Kong ส่งไป Upstream

เมื่อ authentication สำเร็จ Kong สามารถส่งข้อมูล Consumer ไปยัง upstream ผ่าน header ภายในของ Kong

ลองเรียก

curl -s http://localhost:8000/api/headers \
  -H "Authorization: Bearer $JWT_TOKEN"

Upstream เช่น httpbin จะช่วยให้เห็น request headers ที่ได้รับ

แนวคิดคือ backend สามารถทราบว่า request ถูก authenticate ผ่าน Consumer ใดโดยใช้ header ที่ Kong เพิ่มให้

Backend ไม่ควรเชื่อ header ระบุตัวตนที่ส่งตรงจาก public client โดยไม่มี boundary ที่เหมาะสม ควรออกแบบ network และ header trust model ให้ชัดเจน


#Part 7: Negative Testing

#18. Test Case 1 — ไม่มี JWT

curl -i http://localhost:8000/api/get

Expected

401 Unauthorized

#19. Test Case 2 — Token ถูกต้อง

curl -i http://localhost:8000/api/get \
  -H "Authorization: Bearer $JWT_TOKEN"

Expected

200 OK

#20. Test Case 3 — Token ปลอม

curl -i http://localhost:8000/api/get \
  -H "Authorization: Bearer abc.def.xyz"

Expected

401 Unauthorized

#21. Test Case 4 — ใช้ secret ผิด

สร้าง generate_invalid_token.py

import time
import jwt

payload = {
    "iss": "workshop-key",
    "sub": "workshop-user",
    "iat": int(time.time()),
    "exp": int(time.time()) + 3600
}

print(
    jwt.encode(
        payload,
        "wrong-secret",
        algorithm="HS256"
    )
)

เรียก

BAD_TOKEN=$(python generate_invalid_token.py)

curl -i http://localhost:8000/api/get \
  -H "Authorization: Bearer $BAD_TOKEN"

Expected

401 Unauthorized

#22. Test Case 5 — Token หมดอายุ

สร้าง generate_expired_token.py

import time
import jwt

payload = {
    "iss": "workshop-key",
    "sub": "workshop-user",
    "iat": int(time.time()) - 7200,
    "exp": int(time.time()) - 3600
}

print(
    jwt.encode(
        payload,
        "workshop-super-secret-change-me",
        algorithm="HS256"
    )
)

เรียก

EXPIRED_TOKEN=$(python generate_expired_token.py)

curl -i http://localhost:8000/api/get \
  -H "Authorization: Bearer $EXPIRED_TOKEN"

เพราะตั้ง

"claims_to_verify": ["exp"]

Expected

401 Unauthorized

#23. Test Case 6 — iss ไม่ตรงกับ Credential

สร้าง token ที่มี

{
  "iss": "unknown-client"
}

Kong จะหา JWT Credential ที่ตรงกับ key ไม่พบ จึงไม่ยอมให้ request ผ่าน

Expected

401 Unauthorized

#Part 8: JWT Flow แบบละเอียด

sequenceDiagram
    participant C as Client
    participant K as Kong Gateway
    participant P as JWT Plugin
    participant D as Kong Data Store
    participant A as Upstream API

    C->>K: GET /api/get + Bearer JWT
    K->>P: Execute JWT Plugin
    P->>P: Parse Header/Payload
    P->>P: Read iss
    P->>D: Find JWT Credential by key
    D-->>P: key + algorithm + secret/public key
    P->>P: Verify Signature
    P->>P: Verify exp
    alt JWT valid
        P-->>K: Consumer authenticated
        K->>A: Proxy request
        A-->>K: Response
        K-->>C: 200 OK
    else JWT invalid
        P-->>C: 401 Unauthorized
    end

#Part 9: Plugin Scope

Kong Plugin สามารถเปิดได้หลายระดับ

#Global

ใช้กับทุก Route/Service ที่เข้าเงื่อนไข

POST /plugins

#Service

ใช้กับทุก Route ของ Service หนึ่ง

POST /services/{service}/plugins

#Route

ใช้เฉพาะ Route

POST /routes/{route}/plugins

Workshop นี้เลือก Route scope เพื่อไม่กระทบ API อื่น


#Part 10: HS256 กับ RS256

#HS256

ใช้ shared secret

Auth Service --- shared secret --- Kong

ข้อดี

  • ตั้งค่าง่าย
  • เหมาะกับ workshop
  • ประสิทธิภาพดี

ข้อควรระวัง

  • ทั้งฝั่ง signer และ verifier ต้องรู้ secret
  • ถ้า secret รั่ว ผู้ถือ secret สามารถสร้าง token ที่ signature ถูกต้องได้

#RS256

ใช้ asymmetric key pair

Auth Service
   |
   | Sign ด้วย Private Key
   v
JWT
   |
   | Verify ด้วย Public Key
   v
Kong

ข้อดี

  • Kong ไม่จำเป็นต้องรู้ private key
  • แยกหน้าที่ signer กับ verifier ได้ชัดเจน
  • เหมาะกับระบบกระจายตัวมากกว่า

สำหรับ production ที่มี Identity Provider หรือ Auth Service แยกต่างหาก ควรพิจารณา asymmetric signing เช่น RS256 ตาม threat model และ architecture ของระบบ


#Part 11: Declarative Configuration ตัวอย่าง

ตัวอย่างแนวคิดด้วย kong.yaml

_format_version: "3.0"

services:
  - name: demo-api
    url: https://httpbin.org
    routes:
      - name: demo-route
        paths:
          - /api
        strip_path: true

plugins:
  - name: jwt
    route: demo-route
    config:
      header_names:
        - authorization
      key_claim_name: iss
      claims_to_verify:
        - exp

consumers:
  - username: workshop-user
    jwt_secrets:
      - algorithm: HS256
        key: workshop-key
        secret: workshop-super-secret-change-me

Declarative configuration เหมาะกับแนวทาง GitOps เพราะสามารถ version-control configuration และนำไป apply ผ่าน tooling เช่น decK ได้


#Part 12: Security Best Practices

#1. อย่าใช้ secret ตัวอย่างใน production

ตัวอย่าง

workshop-super-secret-change-me

มีไว้เพื่อการศึกษาเท่านั้น

ใช้ secret ที่มี entropy สูงและเก็บใน Secret Manager/Vault


#2. กำหนด exp

JWT สำหรับ access token ไม่ควรมีอายุยาวโดยไม่จำเป็น

ตัวอย่าง

{
  "exp": 1760003600
}

และตั้ง Kong ให้ตรวจ

{
  "claims_to_verify": ["exp"]
}

#3. พิจารณา nbf

Kong สามารถตรวจ nbf ได้ด้วย

{
  "claims_to_verify": [
    "exp",
    "nbf"
  ]
}

#4. ใช้ HTTPS

JWT แม้จะ signed แต่ payload โดยทั่วไปไม่ได้ถูกเข้ารหัส

จึงไม่ควรส่งผ่าน HTTP บน public network

Client
  |
 HTTPS
  |
Kong Gateway

#5. อย่าใส่ข้อมูลลับใน JWT Payload

JWT payload สามารถ decode ได้

หลีกเลี่ยง

{
  "password": "...",
  "credit_card": "...",
  "private_secret": "..."
}

#6. แยก Authentication กับ Authorization

JWT Plugin เน้นการตรวจ identity/token

หากต้องการสิทธิ์ระดับ role/scope เช่น

admin
editor
viewer

ควรออกแบบ authorization policy เพิ่มเติม


#7. ใช้ key rotation

ระบบ production ควรรองรับการหมุน key/secret โดยไม่ทำให้ระบบหยุดทำงาน

แนวทางทั่วไปคือ

Old Key + New Key
        |
 transition period
        |
Remove Old Key

#8. จำกัดการเข้าถึง Admin API

Port

8001

คือ Kong Admin API และไม่ควรเปิดให้ public internet เข้าถึงโดยตรง

ควรจำกัดด้วย

  • private network
  • firewall
  • VPN
  • RBAC/secured administration ตาม edition และ deployment model
  • reverse proxy/internal management plane ตาม architecture

#Part 13: Troubleshooting

#ปัญหา: 401 Unauthorized

ตรวจสอบ

1. มี Authorization header หรือไม่
2. ใช้ Bearer token หรือไม่
3. iss ตรงกับ credential key หรือไม่
4. secret ตรงหรือไม่
5. algorithm ตรงหรือไม่
6. token หมดอายุหรือไม่
7. plugin ถูกผูกกับ Route/Service ที่ถูกต้องหรือไม่

#ตรวจ plugin

curl http://localhost:8001/routes/demo-route/plugins

#ตรวจ Consumer

curl http://localhost:8001/consumers/workshop-user

#ตรวจ JWT Credential

curl http://localhost:8001/consumers/workshop-user/jwt

#ตรวจ Route

curl http://localhost:8001/routes/demo-route

#ตรวจ Service

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

#Part 14: Cleanup

ลบ Route

curl -i -X DELETE http://localhost:8001/routes/demo-route

ลบ Service

curl -i -X DELETE http://localhost:8001/services/demo-api

ลบ Consumer

curl -i -X DELETE http://localhost:8001/consumers/workshop-user

#Part 15: แบบฝึกหัด

#Lab 1

เปลี่ยน Route จาก

/api

เป็น

/secure-api

#Lab 2

เพิ่ม claim

{
  "department": "software-engineering"
}

และตรวจสอบว่าค่า claim ถูก encode อยู่ใน JWT อย่างไร


#Lab 3

ตั้ง JWT ให้หมดอายุใน 60 วินาที

"exp": int(time.time()) + 60

ทดสอบก่อนและหลังหมดอายุ


#Lab 4

เพิ่ม nbf

"nbf": int(time.time()) + 30

และตั้ง plugin

{
  "claims_to_verify": [
    "exp",
    "nbf"
  ]
}

สังเกตผลก่อนและหลังเวลาที่กำหนด


#Lab 5

สร้าง Consumer เพิ่มอีกคน

mobile-app

พร้อม JWT Credential คนละ key/secret และตรวจสอบว่า Kong สามารถแยก Consumer ได้


#Part 16: สรุป

Workflow หลักของ Kong JWT Authentication คือ

1. Create Service
2. Create Route
3. Enable JWT Plugin
4. Create Consumer
5. Create JWT Credential
6. Auth Service/Client สร้าง JWT
7. ส่ง Authorization: Bearer <JWT>
8. Kong อ่าน iss
9. Kong หา Credential
10. Kong ตรวจ Signature
11. Kong ตรวจ exp/nbf
12. Valid -> Proxy ไป Upstream
13. Invalid -> 401 Unauthorized

JWT Plugin ช่วยย้ายงานตรวจ authentication ออกจาก backend ไปไว้ที่ gateway ทำให้หลาย API ใช้นโยบาย authentication ที่สอดคล้องกันได้ง่ายขึ้น อย่างไรก็ตาม production architecture ควรพิจารณาเรื่อง key management, token lifetime, TLS, authorization, secret rotation และการป้องกัน Admin API เพิ่มเติม


#อ้างอิง