daycry / jwt
JWT Token for Codeigniter 4
Requires
- php: ^8.2
- lcobucci/clock: ^3.0
- lcobucci/jwt: ^5.5
- psr/clock: ^1.0
Requires (Dev)
README
A JWT (JSON Web Token) library for CodeIgniter 4, built on top of lcobucci/jwt ^5. Supports HMAC, RSA and ECDSA.
Package
Quality
Community
Requirements
- PHP 8.2 or higher
- CodeIgniter 4.x
lcobucci/jwt ^5.5
Upgrading from v2.x? Read the v2 → v3 migration guide.
Installation
composer require daycry/jwt
Publish the configuration file
php spark jwt:publish
Generate a signing key
php spark jwt:key
The key is written automatically to .env as jwt.signer. Use --show to print it without touching the file.
⚠️ Never commit
.envto version control.
Quick Start
php spark jwt:publish # write app/Config/JWT.php php spark jwt:key # generate jwt.signer in .env
use Daycry\JWT\JWT; $jwt = JWT::for(); // pulls config('JWT') // Encode $token = $jwt->encode(['user_id' => 42, 'role' => 'admin'], 'user-42'); // Decode + validate (throws on failure) $claims = $jwt->decode($token); // Plain echo $claims->claims()->get('uid'); // "user-42" // Symmetric helper — get the original payload back $payload = $jwt->getPayload($token); // ['user_id' => 42, 'role' => 'admin'] // Non-throwing alternative $claims = $jwt->tryDecode($maybeBadToken); if ($claims === null) { return $this->response->setStatusCode(401); }
The library throws
JWTConfigurationExceptionifjwt.signer,jwt.issuer,jwt.audience, orjwt.identifieris empty. Defaults are intentionallynullto fail loudly.
Configuration
After publishing, edit app/Config/JWT.php. All properties are inherited from Daycry\JWT\Config\JWT and overridable via .env.
HMAC (default)
jwt.algorithmType = "symmetric" jwt.signer = "<base64-secret-from-jwt:key>" jwt.issuer = "https://api.my-app.com" jwt.audience = "https://my-app.com" jwt.identifier = "my-app-v2" jwt.expiresAt = "+1 hour" jwt.leeway = "30"
RSA / ECDSA
php spark jwt:keypair --algorithm=rsa --bits=2048 --output=writable/keys
jwt.algorithmType = "asymmetric" jwt.signingKey = "/var/www/app/writable/keys/jwt-private.pem" jwt.verifyingKey = "/var/www/app/writable/keys/jwt-public.pem" jwt.issuer = "https://api.my-app.com" jwt.audience = "https://my-app.com" jwt.identifier = "my-app-v2"
In app/Config/JWT.php set the signer class:
public string $algorithm = \Lcobucci\JWT\Signer\Rsa\Sha256::class; // RS256 // or \Lcobucci\JWT\Signer\Ecdsa\Sha256::class for ES256
See docs/configuration.md for the full reference.
Usage
Compact array payload (default)
$token = $jwt->encode(['user_id' => 1, 'role' => 'admin']); $payload = $jwt->getPayload($token); // ['user_id' => 1, 'role' => 'admin']
Split mode — claims at the top level
$jwt = JWT::for()->withSplitData(); $token = $jwt->encode(['user_id' => 1, 'role' => 'admin']); $claims = $jwt->decode($token); echo $claims->claims()->get('role'); // "admin"
Custom payload claim name
$jwt = JWT::for()->withParamData('payload'); $jwt->getPayload($jwt->encode('hello')); // "hello"
Clock skew tolerance (LooseValidAt)
$jwt = JWT::for()->withLeeway(30); // accept up to ±30s of skew
Error Handling
use Daycry\JWT\Exceptions\InvalidTokenException; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; try { $claims = $jwt->decode($token); } catch (RequiredConstraintsViolated $e) { // Signature, issuer, audience, exp, etc. return $this->response->setStatusCode(401)->setJSON(['error' => $e->getMessage()]); } catch (InvalidTokenException $e) { // Malformed or non-Plain token. return $this->response->setStatusCode(400)->setJSON(['error' => 'Bad token']); }
For a non-throwing flow:
$claims = $jwt->tryDecode($token); if ($claims === null) { return $this->response->setStatusCode(401); }
Utility Methods
| Method | Returns | Description |
|---|---|---|
decode(string $token) |
Plain |
Validates and returns the parsed token. Throws on failure. |
tryDecode(string $token) |
?Plain |
Same as decode() but returns null on failure. |
getPayload(string $token) |
mixed |
Validates + returns the original payload (auto-decoded for compact mode). |
isValid(string $token) |
bool |
True iff tryDecode() succeeds. |
isExpired(string $token) |
bool |
True for malformed tokens or tokens past exp. |
getTimeToExpiry(string $token) |
?int |
Seconds until exp, or null. |
extractClaimsUnsafe(string $token) |
?array |
Claims without validation. Logs a warning unless Config::$allowUnsafeExtraction = true. |
CLI Commands
# Publish config to app/Config/JWT.php php spark jwt:publish # Generate an HMAC key (default 32 bytes) and write to .env php spark jwt:key php spark jwt:key 64 --show php spark jwt:key --force # Generate an asymmetric key pair php spark jwt:keypair --algorithm=rsa --bits=2048 php spark jwt:keypair --algorithm=ecdsa --curve=prime256v1 --output=writable/keys
Security Best Practices
- Use a strong key — at least 32 bytes (
php spark jwt:key). - Set short expiry times for API access tokens (
+15 minutes). - Enable all validation constraints in production.
- Never commit
.envor any file containingjwt.signer. - Rotate the key immediately if exposed — all outstanding tokens become invalid.
Testing
composer test # or without coverage (faster) vendor/bin/phpunit --no-coverage
Documentation
Full reference documentation is in the docs/ folder:
| Document | Description |
|---|---|
| Getting Started | Installation and first token in minutes |
| Configuration | Every property, its type, default, and .env key |
| Usage | Complete API reference with examples |
| CLI Commands | jwt:key and jwt:publish reference |
| Advanced | Utility methods, middleware, multi-tenant patterns |
| Testing | Test suite structure and writing new tests |
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m 'Add my feature' - Push and open a Pull Request
License
MIT — see LICENSE.
Support
- 🐛 Open an issue for bug reports or feature requests
- 💰 Donate via PayPal