Kotchasan Framework Documentation
Jwt Class - JSON Web Token Helper
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\JwtOverview
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'): stringParameters:
$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_hereReal-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']): ?arrayParameters:
$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 passesnull- 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 roleAutomatic 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 exp2. 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:
- HS256 only - No support for RS256, ES256, or other algorithms
- No claims validation - Doesn't validate iss, aud, nbf, jti
- No key rotation - No graceful secret key rotation support
- 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)