beyondbluesky/lib-jwt
libJWT makes JWT tokens easy to encode and decode for OAuth2 authentication workflows. Lightweight PHP library updated for PHP 8.4 (2026-03-28), designed to simplify handling and validating JWTs in your applications.
Installation
composer require beyondbluesky/lib-jwt
Add to composer.json if using a custom package:
"repositories": [{
"type": "vcs",
"url": "https://github.com/beyondbluesky/lib-jwt"
}]
First Use Case: Token Generation
use BeyondBluesky\LibJWT\JWT;
$jwt = new JWT();
$payload = [
'user_id' => 123,
'exp' => time() + 3600, // 1 hour expiry
'iss' => 'your-app-name'
];
$secret = 'your-secret-key'; // Store securely (e.g., .env)
$token = $jwt->encode($payload, $secret);
First Use Case: Token Decoding
$decoded = $jwt->decode($token, $secret);
// Returns array of claims or throws exception on failure
JWT class methods (encode(), decode(), verify()).src/JWT.php for core logic (minimal, ~50 lines).tests/ for usage examples (if available).$jwt = new JWT();
$payload = [
'sub' => 'user@example.com',
'iat' => time(),
'exp' => time() + (60 * 60), // 1 hour
'roles' => ['admin', 'user']
];
$token = $jwt->encode($payload, config('jwt.secret'));
// app/Http/Middleware/AuthenticateJWT.php
public function handle($request, Closure $next) {
$token = $request->bearerToken();
if (!$token) {
return response()->json(['error' => 'Unauthorized'], 401);
}
$jwt = new JWT();
try {
$decoded = $jwt->decode($token, config('jwt.secret'));
$request->merge(['user' => $decoded]);
return $next($request);
} catch (\Exception $e) {
return response()->json(['error' => 'Invalid token'], 401);
}
}
// Generate refresh token with longer expiry
$refreshPayload = [
'sub' => 'user@example.com',
'exp' => time() + (60 * 60 * 24 * 7), // 1 week
'type' => 'refresh'
];
$refreshToken = $jwt->encode($refreshPayload, config('jwt.secret'));
// Use refresh token to get new access token
$decodedRefresh = $jwt->decode($refreshToken, config('jwt.secret'));
if ($decodedRefresh['type'] === 'refresh') {
$accessToken = $jwt->encode($accessPayload, config('jwt.secret'));
}
// app/Providers/AuthServiceProvider.php
protected function boot() {
Passport::tokensExpireIn(Carbon::now()->addDays(15));
Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));
Passport::personalAccessTokensExpireIn(Carbon::now()->addYears(1));
Passport::tokenableModel(User::class, 'oauth_access_tokens');
// Custom JWT guard
Auth::viaRequest('jwt', function ($request) {
$jwt = new JWT();
try {
$token = $request->bearerToken();
$decoded = $jwt->decode($token, config('jwt.secret'));
return User::find($decoded['user_id']);
} catch (\Exception $e) {
return null;
}
});
}
.env for JWT_SECRET:
JWT_SECRET=your-256-bit-secret-key-here
// config/jwt.php
return [
'secret' => env('JWT_SECRET'),
'algorithm' => 'HS256',
'expire' => 3600,
];
JWT class for dependency injection:
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->singleton(JWT::class, function ($app) {
return new JWT();
});
}
No Built-in Key Management
config or a dedicated service (e.g., AWS KMS, Hashicorp Vault).JWT class to fetch secrets dynamically:
class CustomJWT extends JWT {
public function getSecret() {
return config('jwt.secret');
}
}
No Automatic Token Validation
firebase/php-jwt, this library does not validate claims (e.g., exp, nbf) by default. Always manually check:
$decoded = $jwt->decode($token, $secret);
if ($decoded['exp'] < time()) {
throw new Exception('Token expired');
}
PHP 8.4 Compatibility Note
composer require --dev phpunit/phpunit
vendor/bin/phpunit
No Standard Claims Handling
iss, sub), enforce a schema:
$requiredClaims = ['iss', 'sub', 'exp'];
foreach ($requiredClaims as $claim) {
if (!array_key_exists($claim, $decoded)) {
throw new Exception("Missing required claim: {$claim}");
}
}
No Built-in Revocation
// Store refresh tokens in DB with revocation flag
$refreshToken = $jwt->encode($refreshPayload, $secret);
DB::table('refresh_tokens')->insert([
'token' => $refreshToken,
'user_id' => $userId,
'revoked' => false,
]);
Decode Without Verification
Use decode() (not verify()) to inspect tokens:
try {
$decoded = $jwt->decode($token, $secret);
var_dump($decoded); // Debug payload
} catch (\Exception $e) {
var_dump($e->getMessage());
}
Algorithm Mismatch Errors
If using RS256/RS512, ensure the package supports it (it likely doesn’t). Fall back to HS256 or switch libraries:
// Force HS256 if needed
$jwt->setAlgorithm('HS256');
Time Skew Issues If tokens expire prematurely, check server time synchronization:
// Compare with UTC
$decoded['exp'] > time() + 60; // Allow 1-minute buffer
Custom Claims Validation
Extend the JWT class to add validation:
class ValidatingJWT extends JWT {
public function decode($token, $secret) {
$decoded = parent::decode($token, $secret);
$this->validateClaims($decoded);
return $decoded;
}
protected function validateClaims(array $claims) {
if (!isset($claims['iss']) || $claims['iss'] !== config('app.name')) {
throw new Exception('Invalid issuer');
}
}
}
Add Metadata to Tokens Include request context (e.g., IP, user agent):
$payload = [
'user_id' => 123,
'meta' => [
'ip' => request()->ip(),
'user_agent' => request()->userAgent(),
],
];
Logging Decoded Tokens Log payloads for auditing (avoid logging secrets):
$decoded = $jwt->decode($token, $secret);
\Log::info('JWT Decoded', ['claims' => $decoded, 'context' => request()->header()]);
Rate Limiting Combine with
How can I help you explore Laravel packages today?