PHPackages                             daycry/jwt - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. daycry/jwt

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

daycry/jwt
==========

JWT Token for Codeigniter 4

v3.2.0(3w ago)17.1k↑96.7%[4 PRs](https://github.com/daycry/jwt/pulls)4MITPHPPHP ^8.2CI passing

Since Aug 17Pushed 6d ago1 watchersCompare

[ Source](https://github.com/daycry/jwt)[ Packagist](https://packagist.org/packages/daycry/jwt)[ Docs](https://github.com/daycry/jwt)[ RSS](/packages/daycry-jwt/feed)WikiDiscussions master Synced 2d ago

READMEChangelog (10)Dependencies (42)Versions (19)Used By (4)

JWT for CodeIgniter 4
=====================

[](#jwt-for-codeigniter-4)

A JWT (JSON Web Token) library for CodeIgniter 4, built on top of [`lcobucci/jwt ^5`](https://github.com/lcobucci/jwt). Supports HMAC, RSA and ECDSA, an immutable façade, and key rotation.

> 📖 **Full documentation:** **** — this README is a quick overview; the site has the complete, searchable reference.

### Package

[](#package)

[![Latest Stable Version](https://camo.githubusercontent.com/a8bd7020cdb7710e438c7612191f1e2394e7be1cf1f5d241b44be13fc64423b7/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6461796372792f6a77742e7376673f6c6162656c3d737461626c65)](https://packagist.org/packages/daycry/jwt)[![Total Downloads](https://camo.githubusercontent.com/31ec0b2677ffdb695dccfa87215bfd0ec7842d442283b96470a6b9aeb0a85eec/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6461796372792f6a77742e737667)](https://packagist.org/packages/daycry/jwt)[![Monthly Downloads](https://camo.githubusercontent.com/a89b3a9627df04745f8ac7bc281177ec81c7b04b4cb6bf1acbc0a8d0f6239bfc/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646d2f6461796372792f6a77742e737667)](https://packagist.org/packages/daycry/jwt)[![PHP Version Require](https://camo.githubusercontent.com/bc1b21f3cca92a3da300958b1e694c07705af5a8a96856e1a2005fb2618f7892/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f6461796372792f6a77742f7068703f636f6c6f723d383839326266)](https://packagist.org/packages/daycry/jwt)[![License](https://camo.githubusercontent.com/d3bfb46e3d618b6331e0da7a574d12af4832cb0f8a05403bbfd6b3c1d5988438/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6461796372792f6a7774)](https://github.com/daycry/jwt/blob/master/LICENSE)

### Quality

[](#quality)

[![PHP Tests](https://github.com/daycry/jwt/actions/workflows/phpunit.yml/badge.svg)](https://github.com/daycry/jwt/actions/workflows/phpunit.yml)[![PHPStan](https://github.com/daycry/jwt/actions/workflows/phpstan.yml/badge.svg)](https://github.com/daycry/jwt/actions/workflows/phpstan.yml)[![Psalm](https://github.com/daycry/jwt/actions/workflows/psalm.yml/badge.svg)](https://github.com/daycry/jwt/actions/workflows/psalm.yml)[![Rector](https://github.com/daycry/jwt/actions/workflows/rector.yml/badge.svg)](https://github.com/daycry/jwt/actions/workflows/rector.yml)[![Code Style](https://github.com/daycry/jwt/actions/workflows/code-style.yml/badge.svg)](https://github.com/daycry/jwt/actions/workflows/code-style.yml)[![CodeQL](https://github.com/daycry/jwt/actions/workflows/codeql.yml/badge.svg)](https://github.com/daycry/jwt/actions/workflows/codeql.yml)[![Docs](https://github.com/daycry/jwt/actions/workflows/docs.yml/badge.svg)](https://daycry.github.io/jwt/)[![Coverage Status](https://camo.githubusercontent.com/df1c866c16ae41208f6f54e72a8eff1458c26deaebe9e60bacb80d07a5998f00/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f6461796372792f6a77742f62616467652e7376673f6272616e63683d6d6173746572)](https://coveralls.io/github/daycry/jwt?branch=master)

### Community

[](#community)

[![GitHub stars](https://camo.githubusercontent.com/37cdfa314e22c9177fe28fc65bd4ceaca210a235ba2a2226f563727e5d2d1063/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f6461796372792f6a77743f7374796c653d736f6369616c)](https://github.com/daycry/jwt/stargazers)[![Donate](https://camo.githubusercontent.com/b57c445af971e3e99c2d0ccdbf4fa7faa4358ba27fecc8f68459b30289f82eda/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f6e6174652d50617950616c2d626c75652e737667)](https://www.paypal.com/donate?business=SYC5XDT23UZ5G&no_recurring=0&item_name=Thank+you%21&currency_code=EUR)

---

Requirements
------------

[](#requirements)

- PHP **8.2** or higher
- CodeIgniter **4.x**
- `lcobucci/jwt ^5.5`

> Upgrading from v2.x? Read the [v2 → v3 migration guide](docs/migration-v2-to-v3.md).

---

Installation
------------

[](#installation)

```
composer require daycry/jwt
```

### Publish the configuration file

[](#publish-the-configuration-file)

```
php spark jwt:publish
```

### Generate a signing key

[](#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 `.env` to version control.

---

Quick Start
-----------

[](#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')
// or inject an explicit config: new JWT(config('JWT'));

// Encode — the uid may be a string or an integer ID (e.g. a DB primary key)
$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 `JWTConfigurationException` if `jwt.signer`, `jwt.issuer`, `jwt.audience`, or `jwt.identifier` is missing — both `null` and an empty string `""` are rejected. Defaults are intentionally `null` to fail loudly.

---

Configuration
-------------

[](#configuration)

After publishing, edit `app/Config/JWT.php`. All properties are inherited from `Daycry\JWT\Config\JWT` and overridable via `.env`.

### HMAC (default)

[](#hmac-default)

```
jwt.algorithmType = "symmetric"
jwt.signer        = ""
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

[](#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](docs/configuration.md) for the full reference.

---

Usage
-----

[](#usage)

### Compact array payload (default)

[](#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

[](#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

[](#custom-payload-claim-name)

```
$jwt = JWT::for()->withParamData('payload');
$jwt->getPayload($jwt->encode('hello'));  // "hello"
```

### Short-lived tokens (`withExpiresAt`)

[](#short-lived-tokens-withexpiresat)

Override the configured `expiresAt` modifier for a single instance, without mutating the shared config — useful for short-lived access tokens. Like every `with*()` method it returns a new instance. Passing an empty string throws `InvalidArgumentException`.

```
$accessToken = JWT::for()->withExpiresAt('+5 minutes')->encode($data);
```

### Clock skew tolerance (`LooseValidAt`)

[](#clock-skew-tolerance-loosevalidat)

```
$jwt = JWT::for()->withLeeway(30);   // accept up to ±30s of skew
$jwt = JWT::for()->withLeeway(null); // reset to no leeway
```

`withLeeway()` accepts `null` to reset to "no leeway"; a negative value throws `InvalidArgumentException`.

### Per-instance customisers (3.2.0)

[](#per-instance-customisers-320)

Every customiser returns a new instance, leaving the shared config untouched. Override claims, audiences, headers and extra claims per call:

```
$jwt = JWT::for()
    ->withIssuer('https://api.my-app.com')
    ->withAudience('https://app-a.com', 'https://app-b.com') // multiple audiences
    ->withIdentifier(bin2hex(random_bytes(16)))              // unique jti
    ->withClaims(['scope' => 'admin'])                        // extra top-level claims
    ->withHeader('x-trace', $traceId);                        // custom JOSE header

$claims = $jwt->getClaims($token);            // validated array of all claims
$scope  = $jwt->getClaim($token, 'scope');    // validated single claim
```

### Key rotation with `kid` (3.2.0)

[](#key-rotation-with-kid-320)

Tag issued tokens with a `kid` header and verify against a per-`kid` key map, so you can roll keys without invalidating tokens still in flight:

```
// Issuing side — stamp the active key id.
$token = JWT::for()->withKeyId('2026-06')->encode($data);

// Verifying side (app/Config/JWT.php) — accept old and new keys during the window.
public ?string $keyId        = '2026-06';
public array   $verifyingKeys = [
    '2026-05' => '/path/old-public.pem',
    '2026-06' => '/path/new-public.pem',
];
```

On decode, the token's `kid` selects the matching key from `$verifyingKeys` (falling back to `$verifyingKey` / `$signer`). The configured signer/algorithm is always used, so a token's `kid` can never downgrade the verifier.

---

Error Handling
--------------

[](#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);
}
```

### Fail-closed configuration guards

[](#fail-closed-configuration-guards)

The library refuses unsafe configurations up front instead of silently producing weak tokens. `JWTConfigurationException` is thrown when:

- `jwt.validateClaims` does **not** contain `'SignedWith'` while `jwt.validate = true` — `decode()` will not skip signature verification. To decode without any validation, set `jwt.validate = false` (intended for tests/debug only; `decode()` then logs a `warning`).
- `jwt.algorithmType` and `jwt.algorithm` disagree — `'symmetric'` requires an `Lcobucci\JWT\Signer\Hmac\*` signer, `'asymmetric'` requires `Rsa\*` or `Ecdsa\*`. (E.g. leaving the default HMAC `Sha256` on an `'asymmetric'` type is caught with a clear message instead of a cryptic key error.)

An invalid `jwt.canOnlyBeUsedAfter` or `jwt.expiresAt` modifier (anything `DateTimeImmutable::modify()` rejects) throws `InvalidArgumentException` consistently across PHP versions.

---

Utility Methods
---------------

[](#utility-methods)

MethodReturnsDescription`decode(string $token)``Plain`Validates and returns the parsed token. Throws on failure.`tryDecode(string $token)``?Plain`Like `decode()` but returns `null` on a **token** failure. A `JWTConfigurationException` (misconfiguration) still propagates.`getPayload(string $token)``mixed`Validates + returns the original payload (auto-decoded for compact mode).`getClaims(string $token)``array`Validated array of all claims (the safe counterpart of `extractClaimsUnsafe()`).`getClaim(string $token, string $name)``mixed`Validated single claim value (`null` when absent).`isValid(string $token)``bool`True iff `tryDecode()` succeeds.`isExpired(string $token)``bool`True for malformed/expired tokens. **Parses without verifying the signature** — never gate access on it.`getTimeToExpiry(string $token)``?int`Seconds until `exp`, or `null`. **Does not verify the signature.**`extractClaimsUnsafe(string $token)``?array`Claims **without validation**. Logs a warning unless `Config::$allowUnsafeExtraction = true`.---

CLI Commands
------------

[](#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
```

> On Windows, `jwt:keypair` warns that `chmod()` cannot enforce file permissions — restrict the private key with NTFS ACLs (e.g. `icacls`) instead. It also warns when `--passphrase` is passed on the command line, because that value can leak via the process list and shell history; prefer a secrets manager or interactive entry.

---

Security Best Practices
-----------------------

[](#security-best-practices)

1. **Use a strong key** — `php spark jwt:key` enforces a 32-byte (256-bit) minimum, the floor for HS256.
2. **Set short expiry times** for API access tokens (`withExpiresAt('+15 minutes')`).
3. **Enable all validation constraints** in production (keep `'SignedWith'` in `jwt.validateClaims`).
4. **Never commit** `.env` or any file containing `jwt.signer` / private keys.
5. **Rotate keys without downtime** using the `kid` header and `jwt.verifyingKeys` map (see [Key rotation](#key-rotation-with-kid-320)) — keep the old key in the map until its tokens have expired, then drop it. If a key is *leaked*, remove it from the map immediately to revoke its tokens.

---

Testing
-------

[](#testing)

```
composer test
# or without coverage (faster)
vendor/bin/phpunit --no-coverage
```

---

Documentation
-------------

[](#documentation)

📖 **The full, searchable documentation is published at ** (built with MkDocs Material from the [`docs/`](docs/) folder).

DocumentDescription[Getting Started](https://daycry.github.io/jwt/getting-started/)Installation and first token in minutes[Configuration](https://daycry.github.io/jwt/configuration/)Every property, its type, default, and `.env` key[Usage](https://daycry.github.io/jwt/usage/)Complete API reference with examples[Advanced](https://daycry.github.io/jwt/advanced/)Utility methods, key rotation, middleware, multi-tenant patterns[CLI Commands](https://daycry.github.io/jwt/commands/)`jwt:key`, `jwt:keypair`, `jwt:publish` reference[Threat Model](https://daycry.github.io/jwt/threat-model/)Security model and guarantees[Testing](https://daycry.github.io/jwt/testing/)Test suite structure and writing new tests[Migration v2 → v3](https://daycry.github.io/jwt/migration-v2-to-v3/)Upgrade guide---

Contributing
------------

[](#contributing)

1. Fork the repository
2. Create a feature branch: `git checkout -b feature/my-feature`
3. Commit your changes: `git commit -m 'Add my feature'`
4. Push and open a Pull Request

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

---

Support
-------

[](#support)

- 🐛 [Open an issue](https://github.com/daycry/jwt/issues) for bug reports or feature requests
- 💰 [Donate via PayPal](https://www.paypal.com/donate?business=SYC5XDT23UZ5G&no_recurring=0&item_name=Thank+you%21&currency_code=EUR)

###  Health Score

55

—

FairBetter than 97% of packages

Maintenance97

Actively maintained with recent releases

Popularity26

Limited adoption so far

Community16

Small or concentrated contributor base

Maturity69

Established project with proven stability

 Bus Factor1

Top contributor holds 90.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~126 days

Recently: every ~77 days

Total

12

Last Release

27d ago

Major Versions

v1.0.6 → v2.0.02025-07-31

v2.0.1 → v3.0.02026-05-08

PHP version history (3 changes)v1.0.0PHP &gt;=7.4 || ^8.0

v2.0.0PHP ^8.1

v3.0.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/3b0f66565d5c9ca3c84fb294e04f8d5e0b9a867d9c06f83b95bf168bd6fcf9bc?d=identicon)[daycry](/maintainers/daycry)

---

Top Contributors

[![daycry](https://avatars.githubusercontent.com/u/7590335?v=4)](https://github.com/daycry "daycry (48 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (5 commits)")

---

Tags

jwtauthJSON Web Tokencodeigniter4Bearerlcobucci

###  Code Quality

Static AnalysisPsalm

Type Coverage Yes

### Embed Badge

![Health badge](/badges/daycry-jwt/health.svg)

```
[![Health](https://phpackages.com/badges/daycry-jwt/health.svg)](https://phpackages.com/packages/daycry-jwt)
```

###  Alternatives

[tymon/jwt-auth

JSON Web Token Authentication for Laravel and Lumen

11.7k51.8M371](/packages/tymon-jwt-auth)[php-open-source-saver/jwt-auth

JSON Web Token Authentication for Laravel and Lumen

84611.1M63](/packages/php-open-source-saver-jwt-auth)[maicol07/flarum-ext-sso

SSO for Flarum

468.7k](/packages/maicol07-flarum-ext-sso)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
