Kotchasan Framework Documentation

Kotchasan Framework Documentation

คลาส Jwt - JSON Web Token Helper

TH 03 Feb 2026 15:32

คลาส 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];  // ไม่มี exp

2. 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 มีข้อจำกัดดังนี้:

  1. รองรับเฉพาะ HS256 - ไม่รองรับ RS256, ES256, หรืออัลกอริทึมอื่นๆ
  2. ไม่มี claims validation - ไม่ตรวจสอบ iss, aud, nbf, jti
  3. ไม่มี key rotation - ไม่รองรับการเปลี่ยน secret key แบบ graceful
  4. ไม่มี 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)