Kotchasan Framework Documentation
คลาส Jwt - JSON Web Token Helper
คลาส Jwt - JSON Web Token Helper
คลาส Jwt เป็นยูทิลิตี้ขนาดเล็กสำหรับการสร้างและตรวจสอบ JSON Web Token (JWT) โดยรองรับอัลกอริทึม HS256 เหมาะสำหรับการใช้งานพื้นฐานและการย้ายระบบไปใช้ JWT
Namespace
Kotchasan\Jwtภาพรวม
Jwt class เป็น JWT helper แบบ minimal ที่:
- รองรับอัลกอริทึม HS256 (HMAC SHA-256)
- สร้าง JWT tokens
- ตรวจสอบและถอดรหัส JWT tokens
- ตรวจสอบ expiration time อัตโนมัติ
- ใช้งานง่าย เหมาะสำหรับ API authentication
[!NOTE]
คลาสนี้เป็น minimal implementation ไม่ใช่ full-featured JWT library เหมาะสำหรับการใช้งานพื้นฐานและเป็น starter สำหรับการย้ายไปใช้ JWT
Public Methods
encode()
สร้าง JWT token จาก payload
public static function encode(array $payload, string $secret, string $algo = 'HS256'): stringพารามิเตอร์:
$payload- array ของข้อมูลที่ต้องการเข้ารหัส$secret- secret key สำหรับ signing$algo- อัลกอริทึม (default: 'HS256')
คืนค่า: JWT token string
ตัวอย่าง:
use Kotchasan\Jwt;
// สร้าง JWT token พื้นฐาน
$payload = [
'user_id' => 123,
'username' => 'john_doe',
'role' => 'admin'
];
$secret = 'your-secret-key-keep-it-safe';
$token = Jwt::encode($payload, $secret);
echo $token;
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoiam9obl9kb2UiLCJyb2xlIjoiYWRtaW4ifQ.signature_hereตัวอย่างการใช้งานจริง:
use Kotchasan\Jwt;
// 1. Login และสร้าง token
function login($username, $password)
{
// ตรวจสอบ credentials
$user = verify_credentials($username, $password);
if ($user) {
$payload = [
'user_id' => $user['id'],
'username' => $user['username'],
'role' => $user['role'],
'iat' => time(), // Issued at
'exp' => time() + (60 * 60 * 24) // หมดอายุใน 24 ชั่วโมง
];
$secret = getenv('JWT_SECRET');
$token = Jwt::encode($payload, $secret);
return [
'success' => true,
'token' => $token,
'expires_in' => 86400 // 24 hours in seconds
];
}
return ['success' => false, 'message' => 'Invalid credentials'];
}
// 2. สร้าง token สำหรับ API
$apiPayload = [
'app_id' => 'mobile-app-v1',
'permissions' => ['read', 'write'],
'iat' => time(),
'exp' => time() + (60 * 60) // หมดอายุใน 1 ชั่วโมง
];
$apiToken = Jwt::encode($apiPayload, $secret);Payload Fields มาตรฐาน:
// Claims ที่แนะนำตาม JWT specification
$payload = [
// Registered claims
'iss' => 'https://your-domain.com', // Issuer
'sub' => 'user_123', // Subject
'aud' => 'https://api.example.com', // Audience
'exp' => time() + 3600, // Expiration time
'nbf' => time(), // Not before
'iat' => time(), // Issued at
'jti' => uniqid(), // JWT ID (unique identifier)
// Custom claims (ข้อมูลของคุณเอง)
'user_id' => 123,
'username' => 'john',
'role' => 'admin',
'permissions' => ['read', 'write', 'delete']
];
$token = Jwt::encode($payload, $secret);decode()
ตรวจสอบและถอดรหัส JWT token
public static function decode(string $jwt, string $secret, array $allowedAlgos = ['HS256']): ?arrayพารามิเตอร์:
$jwt- JWT token string$secret- secret key (ต้องตรงกับตอนสร้าง token)$allowedAlgos- อัลกอริทึมที่อนุญาต (default: ['HS256'])
คืนค่า:
array- payload ถ้าตรวจสอบผ่านnull- ถ้า token ไม่ถูกต้องหรือหมดอายุ
ตัวอย่าง:
use Kotchasan\Jwt;
$token = 'eyJ0eXAiOiJKV1QiLCJhbGc...';
$secret = 'your-secret-key-keep-it-safe';
// ถอดรหัส token
$payload = Jwt::decode($token, $secret);
if ($payload) {
echo "User ID: " . $payload['user_id'];
echo "Username: " . $payload['username'];
echo "Role: " . $payload['role'];
} else {
echo "Invalid or expired token";
}ตัวอย่างการใช้งานจริง:
use Kotchasan\Jwt;
// 1. Middleware สำหรับตรวจสอบ authentication
function authenticate()
{
// ดึง token จาก header
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';
if (preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
$token = $matches[1];
$secret = getenv('JWT_SECRET');
$payload = Jwt::decode($token, $secret);
if ($payload) {
// Token ถูกต้อง - บันทึกข้อมูล user
$_SESSION['user_id'] = $payload['user_id'];
$_SESSION['role'] = $payload['role'];
return true;
}
}
// Token ไม่ถูกต้อง
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
// 2. ตรวจสอบ permissions
function requireRole($requiredRole)
{
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';
if (preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
$token = $matches[1];
$payload = Jwt::decode($token, getenv('JWT_SECRET'));
if ($payload && ($payload['role'] ?? '') === $requiredRole) {
return true;
}
}
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
exit;
}
// ใช้งาน
authenticate();
requireRole('admin');
// Code ที่ต้องการ admin roleการตรวจสอบที่ decode() ทำอัตโนมัติ:
// decode() จะตรวจสอบ:
// 1. Token format (ต้องเป็น 3 ส่วนคั่นด้วย .)
if (count($parts) != 3) return null;
// 2. Header และ Payload ต้อง decode ได้
if (empty($header) || empty($payload)) return null;
// 3. Algorithm ต้องอยู่ใน allowed list
if (!in_array($header['alg'], $allowedAlgos)) return null;
// 4. Signature ต้องถูกต้อง
if (!hash_equals($expected, $sig)) return null;
// 5. Expiration time (ถ้ามี 'exp' claim)
if (isset($payload['exp']) && time() >= $payload['exp']) return null;ตัวอย่างการใช้งานครบวงจร
1. API Authentication System
use Kotchasan\Jwt;
class AuthController
{
private $secret;
public function __construct()
{
$this->secret = getenv('JWT_SECRET') ?: 'default-secret-change-in-production';
}
/**
* Login และสร้าง token
*/
public function login($username, $password)
{
// ตรวจสอบ credentials
$user = $this->verifyUser($username, $password);
if (!$user) {
return $this->jsonResponse([
'success' => false,
'message' => 'Invalid credentials'
], 401);
}
// สร้าง payload
$payload = [
'user_id' => $user['id'],
'username' => $user['username'],
'email' => $user['email'],
'role' => $user['role'],
'iat' => time(),
'exp' => time() + (60 * 60 * 24) // 24 hours
];
// สร้าง token
$token = Jwt::encode($payload, $this->secret);
return $this->jsonResponse([
'success' => true,
'token' => $token,
'user' => [
'id' => $user['id'],
'username' => $user['username'],
'role' => $user['role']
],
'expires_in' => 86400
]);
}
/**
* ตรวจสอบ token
*/
public function verify($token)
{
$payload = Jwt::decode($token, $this->secret);
if (!$payload) {
return $this->jsonResponse([
'success' => false,
'message' => 'Invalid or expired token'
], 401);
}
return $this->jsonResponse([
'success' => true,
'user' => [
'id' => $payload['user_id'],
'username' => $payload['username'],
'role' => $payload['role']
]
]);
}
/**
* Refresh token
*/
public function refresh($oldToken)
{
$payload = Jwt::decode($oldToken, $this->secret);
if (!$payload) {
return $this->jsonResponse([
'success' => false,
'message' => 'Invalid token'
], 401);
}
// สร้าง token ใหม่
$newPayload = [
'user_id' => $payload['user_id'],
'username' => $payload['username'],
'email' => $payload['email'] ?? '',
'role' => $payload['role'],
'iat' => time(),
'exp' => time() + (60 * 60 * 24)
];
$newToken = Jwt::encode($newPayload, $this->secret);
return $this->jsonResponse([
'success' => true,
'token' => $newToken,
'expires_in' => 86400
]);
}
private function jsonResponse($data, $code = 200)
{
http_response_code($code);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
private function verifyUser($username, $password)
{
// Implement your user verification logic
// Return user array or false
}
}
// ใช้งาน
$auth = new AuthController();
// POST /api/login
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$auth->login($data['username'], $data['password']);
}
// POST /api/verify
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['action']) && $_GET['action'] === 'verify') {
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';
if (preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
$auth->verify($matches[1]);
}
}2. Middleware Pattern
use Kotchasan\Jwt;
class JwtMiddleware
{
private static $secret;
private static $currentUser;
public static function init($secret)
{
self::$secret = $secret;
}
/**
* ตรวจสอบ authentication
*/
public static function authenticate()
{
$token = self::extractToken();
if (!$token) {
self::unauthorized('Token not provided');
}
$payload = Jwt::decode($token, self::$secret);
if (!$payload) {
self::unauthorized('Invalid or expired token');
}
self::$currentUser = $payload;
return $payload;
}
/**
* ตรวจสอบ role
*/
public static function requireRole($role)
{
$user = self::$currentUser ?? self::authenticate();
if (($user['role'] ?? '') !== $role) {
self::forbidden('Insufficient permissions');
}
return $user;
}
/**
* ตรวจสอบ permission
*/
public static function requirePermission($permission)
{
$user = self::$currentUser ?? self::authenticate();
$permissions = $user['permissions'] ?? [];
if (!in_array($permission, $permissions)) {
self::forbidden('Permission denied');
}
return $user;
}
/**
* ดึง current user
*/
public static function getUser()
{
return self::$currentUser ?? self::authenticate();
}
/**
* ดึง token จาก header
*/
private static function extractToken()
{
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';
if (preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
return $matches[1];
}
return null;
}
private static function unauthorized($message)
{
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['error' => $message]);
exit;
}
private static function forbidden($message)
{
http_response_code(403);
header('Content-Type: application/json');
echo json_encode(['error' => $message]);
exit;
}
}
// ใช้งาน
JwtMiddleware::init(getenv('JWT_SECRET'));
// Protected route ทั่วไป
JwtMiddleware::authenticate();
// Protected route สำหรับ admin
JwtMiddleware::requireRole('admin');
// Protected route ต้องการ permission
JwtMiddleware::requirePermission('delete_user');
// ดึงข้อมูล user
$user = JwtMiddleware::getUser();
echo "Hello, " . $user['username'];3. Multi-tenant Application
use Kotchasan\Jwt;
// สร้าง token สำหรับ tenant
function createTenantToken($userId, $tenantId, $role)
{
$payload = [
'user_id' => $userId,
'tenant_id' => $tenantId,
'role' => $role,
'iat' => time(),
'exp' => time() + (60 * 60 * 8) // 8 hours
];
$secret = getTenantSecret($tenantId);
return Jwt::encode($payload, $secret);
}
// ตรวจสอบ token และ tenant
function verifyTenantToken($token, $expectedTenantId)
{
$secret = getTenantSecret($expectedTenantId);
$payload = Jwt::decode($token, $secret);
if (!$payload) {
return false;
}
// ตรวจสอบว่า token ตรงกับ tenant
if (($payload['tenant_id'] ?? '') !== $expectedTenantId) {
return false;
}
return $payload;
}
function getTenantSecret($tenantId)
{
// ดึง secret เฉพาะของแต่ละ tenant
// อาจเก็บใน database หรือ config
return "tenant_{$tenantId}_secret_key";
}Best Practices
1. ความปลอดภัย (Security)
// ✅ ดี: ใช้ secret key ที่แข็งแรง
$secret = bin2hex(random_bytes(32)); // 256-bit key
$token = Jwt::encode($payload, $secret);
// ❌ ไม่ดี: ใช้ secret ที่เดาง่าย
$secret = 'my-secret'; // อันตราย!// ✅ ดี: เก็บ secret ใน environment variable
$secret = getenv('JWT_SECRET');
// ❌ ไม่ดี: hard-code secret ในโค้ด
$secret = 'abc123'; // อันตราย!// ✅ ดี: ตั้งค่า expiration time
$payload = [
'user_id' => 123,
'exp' => time() + (60 * 60) // หมดอายุใน 1 ชั่วโมง
];
// ⚠️ ระวัง: ไม่ตั้ง expiration (token ใช้ได้ตลอด)
$payload = ['user_id' => 123]; // ไม่มี exp2. Token Management
// ✅ ดี: ใช้ short-lived access token + refresh token pattern
$accessToken = Jwt::encode([
'user_id' => 123,
'type' => 'access',
'exp' => time() + (60 * 15) // 15 minutes
], $secret);
$refreshToken = Jwt::encode([
'user_id' => 123,
'type' => 'refresh',
'exp' => time() + (60 * 60 * 24 * 7) // 7 days
], $secret);
// ✅ ดี: เก็บ sensitive data แยกจาก token
$payload = [
'user_id' => 123, // เก็บแค่ ID
'role' => 'admin'
// ไม่เก็บ password, credit card, etc.
];3. Error Handling
// ✅ ดี: ตรวจสอบ token อย่างรอบคอบ
function getUserFromToken($token)
{
try {
$payload = Jwt::decode($token, $secret);
if (!$payload) {
throw new Exception('Invalid token');
}
// ตรวจสอบเพิ่มเติม
if (!isset($payload['user_id'])) {
throw new Exception('Invalid token payload');
}
return $payload;
} catch (Exception $e) {
error_log('JWT Error: ' . $e->getMessage());
return null;
}
}
// ✅ ดี: จัดการ different error cases
$payload = Jwt::decode($token, $secret);
if ($payload === null) {
// Token อาจจะ:
// - Format ไม่ถูกต้อง
// - Signature ไม่ถูกต้อง
// - หมดอายุแล้ว
// - Algorithm ไม่ตรง
// ส่ง response ที่เหมาะสม
http_response_code(401);
echo json_encode(['error' => 'Invalid or expired token']);
}4. Testing
// ✅ ดี: เขียน test สำหรับ JWT
function testJwtEncodeDecode()
{
$payload = ['user_id' => 123, 'exp' => time() + 3600];
$secret = 'test-secret';
// Test encode
$token = Jwt::encode($payload, $secret);
assert(!empty($token));
assert(count(explode('.', $token)) === 3);
// Test decode
$decoded = Jwt::decode($token, $secret);
assert($decoded !== null);
assert($decoded['user_id'] === 123);
// Test wrong secret
$invalid = Jwt::decode($token, 'wrong-secret');
assert($invalid === null);
// Test expired token
$expiredPayload = ['user_id' => 123, 'exp' => time() - 1];
$expiredToken = Jwt::encode($expiredPayload, $secret);
$result = Jwt::decode($expiredToken, $secret);
assert($result === null);
echo "All tests passed!\n";
}ข้อจำกัด
[!WARNING]
คลาส Jwt นี้เป็น minimal implementation มีข้อจำกัดดังนี้:
- รองรับเฉพาะ HS256 - ไม่รองรับ RS256, ES256, หรืออัลกอริทึมอื่นๆ
- ไม่มี claims validation - ไม่ตรวจสอบ iss, aud, nbf, jti
- ไม่มี key rotation - ไม่รองรับการเปลี่ยน secret key แบบ graceful
- ไม่มี token revocation - ไม่สามารถบังคับให้ token หมดอายุก่อนเวลา
สำหรับการใช้งาน production ที่ซับซ้อน แนะนำให้ใช้ library เต็มรูปแบบ เช่น firebase/php-jwt
สรุป
คลาส Jwt เป็น helper ขนาดเล็กที่:
- ✅ ใช้งานง่าย - เพียง 2 methods (encode, decode)
- ✅ ปลอดภัย - ใช้ HMAC SHA-256
- ✅ มาตรฐาน - รองรับ JWT specification
- ✅ มี expiration check - ตรวจสอบหมดอายุอัตโนมัติ
- ⚡ เบา - เพียง 101 บรรทัด
- ⚠️ Minimal - เหมาะสำหรับการใช้งานพื้นฐาน
เหมาะสำหรับ:
- API authentication
- Stateless sessions
- Single sign-on (SSO) แบบง่าย
- Microservices authentication
ขนาดไฟล์: 101 บรรทัด
Public Methods: 2 methods (encode, decode)
Algorithm: HS256 (HMAC SHA-256)