Kotchasan Framework Documentation

Kotchasan Framework Documentation

Jwt Class - JSON Web Token Helper

EN 05 Feb 2026 02:15

Jwt Class - JSON Web Token Helper

The Jwt class is a minimal utility for creating and validating JSON Web Tokens (JWT) with HS256 algorithm support. It's suitable for basic usage and migrating systems to JWT.

Namespace

Kotchasan\Jwt

Overview

The Jwt class is a minimal JWT helper that:

  • Supports HS256 algorithm (HMAC SHA-256)
  • Creates JWT tokens
  • Validates and decodes JWT tokens
  • Automatically checks expiration time
  • Easy to use, perfect for API authentication

[!NOTE]
This class is a minimal implementation, not a full-featured JWT library. It's suitable for basic usage and as a starter for migrating to JWT.

Public Methods

encode()

Create a JWT token from payload

public static function encode(array $payload, string $secret, string $algo = 'HS256'): string

Parameters:

  • $payload - array of data to encode
  • $secret - secret key for signing
  • $algo - algorithm (default: 'HS256')

Returns: JWT token string

Example:

use Kotchasan\Jwt;

// Create basic 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

Real-world Usage:

use Kotchasan\Jwt;

// 1. Login and create token
function login($username, $password)
{
    // Verify 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)   // Expires in 24 hours
        ];

        $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. Create API token
$apiPayload = [
    'app_id' => 'mobile-app-v1',
    'permissions' => ['read', 'write'],
    'iat' => time(),
    'exp' => time() + (60 * 60)  // Expires in 1 hour
];

$apiToken = Jwt::encode($apiPayload, $secret);

Standard Payload Fields:

// Recommended claims per 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 (your own data)
    'user_id' => 123,
    'username' => 'john',
    'role' => 'admin',
    'permissions' => ['read', 'write', 'delete']
];

$token = Jwt::encode($payload, $secret);

decode()

Validate and decode JWT token

public static function decode(string $jwt, string $secret, array $allowedAlgos = ['HS256']): ?array

Parameters:

  • $jwt - JWT token string
  • $secret - secret key (must match the one used to create the token)
  • $allowedAlgos - allowed algorithms (default: ['HS256'])

Returns:

  • array - payload if validation passes
  • null - if token is invalid or expired

Example:

use Kotchasan\Jwt;

$token = 'eyJ0eXAiOiJKV1QiLCJhbGc...';
$secret = 'your-secret-key-keep-it-safe';

// Decode 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";
}

Real-world Usage:

use Kotchasan\Jwt;

// 1. Authentication middleware
function authenticate()
{
    // Extract token from 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 valid - store user data
            $_SESSION['user_id'] = $payload['user_id'];
            $_SESSION['role'] = $payload['role'];
            return true;
        }
    }

    // Invalid token
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

// 2. Check 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;
}

// Usage
authenticate();
requireRole('admin');

// Code requiring admin role

Automatic Validation by decode():

// decode() automatically checks:

// 1. Token format (must be 3 parts separated by .)
if (count($parts) != 3) return null;

// 2. Header and Payload must be decodable
if (empty($header) || empty($payload)) return null;

// 3. Algorithm must be in allowed list
if (!in_array($header['alg'], $allowedAlgos)) return null;

// 4. Signature must be valid
if (!hash_equals($expected, $sig)) return null;

// 5. Expiration time (if 'exp' claim exists)
if (isset($payload['exp']) && time() >= $payload['exp']) return null;

Complete Usage Examples

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 and create token
     */
    public function login($username, $password)
    {
        // Verify credentials
        $user = $this->verifyUser($username, $password);

        if (!$user) {
            return $this->jsonResponse([
                'success' => false,
                'message' => 'Invalid credentials'
            ], 401);
        }

        // Create payload
        $payload = [
            'user_id' => $user['id'],
            'username' => $user['username'],
            'email' => $user['email'],
            'role' => $user['role'],
            'iat' => time(),
            'exp' => time() + (60 * 60 * 24)  // 24 hours
        ];

        // Create 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
        ]);
    }

    /**
     * Verify 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);
        }

        // Create new 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
        ]);
    }
}

2. Middleware Pattern

use Kotchasan\Jwt;

class JwtMiddleware
{
    private static $secret;
    private static $currentUser;

    public static function init($secret)
    {
        self::$secret = $secret;
    }

    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;
    }

    public static function requireRole($role)
    {
        $user = self::$currentUser ?? self::authenticate();

        if (($user['role'] ?? '') !== $role) {
            self::forbidden('Insufficient permissions');
        }

        return $user;
    }
}

Best Practices

1. Security

// ✅ Good: Use strong secret key
$secret = bin2hex(random_bytes(32));  // 256-bit key
$token = Jwt::encode($payload, $secret);

// ❌ Bad: Use weak secret
$secret = 'my-secret';  // Dangerous!
// ✅ Good: Store secret in environment variable
$secret = getenv('JWT_SECRET');

// ❌ Bad: Hard-code secret in code
$secret = 'abc123';  // Dangerous!
// ✅ Good: Set expiration time
$payload = [
    'user_id' => 123,
    'exp' => time() + (60 * 60)  // Expires in 1 hour
];

// ⚠️ Warning: No expiration (token valid forever)
$payload = ['user_id' => 123];  // No exp

2. Token Management

// ✅ Good: Use 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);

// ✅ Good: Keep sensitive data separate from token
$payload = [
    'user_id' => 123,  // Store only ID
    'role' => 'admin'
    // Don't store password, credit card, etc.
];

Limitations

[!WARNING]
This Jwt class is a minimal implementation with limitations:

  1. HS256 only - No support for RS256, ES256, or other algorithms
  2. No claims validation - Doesn't validate iss, aud, nbf, jti
  3. No key rotation - No graceful secret key rotation support
  4. No token revocation - Cannot force token expiration before time

For complex production usage, use a full-featured library like firebase/php-jwt

Summary

The Jwt class is a lightweight helper that:

  • ✅ Easy to use - Only 2 methods (encode, decode)
  • ✅ Secure - Uses HMAC SHA-256
  • ✅ Standard - Supports JWT specification
  • ✅ Has expiration check - Automatic expiration validation
  • ⚡ Lightweight - Only 101 lines
  • ⚠️ Minimal - Suitable for basic usage

Suitable for:

  • API authentication
  • Stateless sessions
  • Simple single sign-on (SSO)
  • Microservices authentication

File Size: 101 lines
Public Methods: 2 methods (encode, decode)
Algorithm: HS256 (HMAC SHA-256)