PHPackages                             phithi92/json-web-token - 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. phithi92/json-web-token

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

phithi92/json-web-token
=======================

Handles the generation of JSON Web Tokens (JWT), including both JWS (JSON Web Signature) and JWE (JSON Web Encryption). Provides methods for creating and validating tokens.

v2.1.1(2mo ago)210MITPHPPHP &gt;=8.2CI passing

Since Oct 19Pushed 1mo ago2 watchersCompare

[ Source](https://github.com/phithi92/json-web-token)[ Packagist](https://packagist.org/packages/phithi92/json-web-token)[ RSS](/packages/phithi92-json-web-token/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (6)Dependencies (10)Versions (17)Used By (0)

[![PHP Version](https://camo.githubusercontent.com/59e075ff803f3f8dd2f15b40354cec2d86f3f5cfa6c2eaf4446634338b4e99b8/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f70686974686939322f6a736f6e2d7765622d746f6b656e2e7376673f7374796c653d666f722d7468652d6261646765)](https://packagist.org/packages/phithi92/json-web-token) [![Latest Version](https://camo.githubusercontent.com/c1f596feff9b41226b49bdb249e47084d3f8d2bfc4b6b1aa6a7e2fd3e018c7a1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f70686974686939322f6a736f6e2d7765622d746f6b656e2e7376673f7374796c653d666f722d7468652d6261646765)](https://github.com/phithi92/json-web-token/releases) [![Software License](https://camo.githubusercontent.com/9897f4467850972a38c7db9a4d38280b8fcdac0ada00e9c8c0a72ecfa8551653/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666f722d7468652d6261646765)](LICENSE) [![Issues](https://camo.githubusercontent.com/e15b8509826a2d6e7eba928ec9214cc9a121ed5442fc8dae5fbadb05adc4ad26/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f70686974686939322f6a736f6e2d7765622d746f6b656e2e7376673f7374796c653d666f722d7468652d6261646765)](https://github.com/phithi92/json-web-token/issues) [![Build](https://camo.githubusercontent.com/b62553f7dcb19440d3edb17e6220295a8bb9a5c2e143d9819d7d0db5e7cc231b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f70686974686939322f6a736f6e2d7765622d746f6b656e2f7068702e796d6c3f6272616e63683d6d61696e267374796c653d666f722d7468652d6261646765)](https://github.com/phithi92/json-web-token/actions) [![Total Downloads](https://camo.githubusercontent.com/4ed1a9509f68b8b5f237efe13019574640a5cfac252cbbc88559599a6d55789d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f70686974686939322f6a736f6e2d7765622d746f6b656e2e7376673f7374796c653d666f722d7468652d6261646765)](https://packagist.org/packages/phithi92/json-web-token)

JSON Web Token (JWT) Library
============================

[](#json-web-token-jwt-library)

**Security-first JWT implementation for PHP 8.2+.**
Create, sign, encrypt, decrypt, validate, and reissue JSON Web Tokens with explicit key management and strict defaults.

Supports **JWS** and **JWE**, a pluggable algorithm registry, and fine-grained claim validation—without hiding security decisions behind magic defaults.

---

Highlights
----------

[](#highlights)

- ✅ RFC-compliant (JWS, JWE, JWA, JWT, JWK)
- 🔐 Secure-by-default claim validation and key handling
- 🧩 Clear separation of concerns (keys, payloads, algorithms, validation)
- 🔁 Built-in reissue / refresh workflows
- 🧪 Explicit *testing-only* escape hatches

---

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

[](#installation)

```
composer require phithi92/json-web-token:^2.0
```

### Requirements

[](#requirements)

- PHP **8.2+**
- OpenSSL extension (required)
- `phpseclib/phpseclib` (installed automatically)

---

Interoperability
----------------

[](#interoperability)

Tokens produced by this library are fully RFC-compliant and interoperable with other JWT implementations across different languages and platforms.

No proprietary headers, claims, or encoding shortcuts are introduced. As long as the same algorithms, keys, and claims are used, tokens can be safely exchanged with other standards-compliant JWT stacks.

---

Supported RFCs
--------------

[](#supported-rfcs)

- **RFC 7515** — JSON Web Signature (JWS)
- **RFC 7516** — JSON Web Encryption (JWE)
- **RFC 7517** — JSON Web Key (JWK, reference formats)
- **RFC 7518** — JSON Web Algorithms (JWA)
- **RFC 7519** — JSON Web Token (JWT)
- **RFC 7638** — JWK Thumbprints (`kid` derivation)

---

Architecture Overview
---------------------

[](#architecture-overview)

```
JwtKeyManager        → keys, algorithms, passphrases
JwtPayload           → claims & type-safe helpers
JwtTokenService      → create / decrypt / reissue
JwtValidator         → issuer, audience, claims, replay protection
JwtBundle            → parsed token aggregate

```

Each component is usable independently, but the default factory wires everything safely for you.

---

`JwtTokenService` Default Wiring (`createDefault()`)
----------------------------------------------------

[](#jwttokenservice-default-wiring-createdefault)

`JwtTokenServiceFactory::createDefault()` is intentionally opinionated and builds a **consistent default dependency graph** so all operations share the same baseline behavior.

Internally, it creates:

- one shared `JwtValidator` instance (default: no expected issuer/audience, no clock skew, no private-claim expectations, no JTI registry)
- one `JwtPayloadCodec`
- one `JwtTokenIssuerFactory`
- one `JwtTokenDecryptorFactory`
- one `JwtTokenCreator` (with the shared default validator)
- one `JwtTokenReader`
- one `JwtClaimsValidationService` (with the shared default validator)
- one `JwtTokenReissuer` (with the shared default validator)

This means:

- Passing `null` as validator uses the shared default validator of this service instance.
- `createDefault()` returns a **fresh service graph per call** (instances are not reused globally).
- Claim validation is only as strict as your configured `JwtValidator`; for production you should usually pass an explicit validator with issuer/audience/JTI expectations.

> `createTokenWithoutClaimValidation()` and `decryptTokenWithoutClaimValidation()` are intentionally unsafe escape hatches for tests/tooling.

---

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

[](#quick-start)

### 1) Configure Keys / Algorithms

[](#1-configure-keys--algorithms)

`JwtKeyManager` holds all keys in memory. **Asymmetric keys must be PEM-encoded.**Symmetric secrets (HMAC, `dir`) live in the passphrase store.

```
use Phithi92\JsonWebToken\Security\KeyManagement\JwtKeyManager;

$manager = new JwtKeyManager();

$manager->addKeyPair(
    private: file_get_contents('/path/private.pem'),
    public: file_get_contents('/path/public.pem'),
    kid: 'main-key'
);

$manager->addPassphrase(
    passphrase: getenv('JWT_KEY_PASSPHRASE'),
    kid: 'main-key'
);

// For symmetric algorithms (HS*, dir/A*GCM), register a shared secret
$manager->addPassphrase(
    passphrase: getenv('JWT_SHARED_SECRET'),
    kid: 'HS256'
);
```

> If no `kid` is provided when issuing tokens, one is derived from the JOSE header (e.g. `RS256`, `RSA-OAEP-256.A256GCM`). Make sure the corresponding key is registered under that `kid`, or pass a `kid` explicitly.

---

### 2) Build a Payload

[](#2-build-a-payload)

`JwtPayload` provides helpers for standard claims and strict validation.

```
use Phithi92\JsonWebToken\Token\JwtPayload;

$payload = (new JwtPayload())
    ->setIssuer('https://issuer.example')
    ->setAudience('https://service.example')
    ->setIssuedAt('now')
    ->setExpiration('+15 minutes')
    ->setJwtId('token-123')
    ->addClaim('role', 'admin');
```

Time-based helper setters such as `setIssuedAt()` and `setExpiration()` accept date/time strings, for example:

- `"now"`
- Relative strings (`+15 minutes`)
- Absolute datetime strings (`2026-01-01T00:00:00+00:00`)

> ℹ️ JWT `iat`, `nbf`, and `exp` are NumericDate values (seconds since Unix epoch in UTC). Always generate and compare timestamps in UTC to avoid timezone drift.

If you want to set UNIX timestamps directly, use `setClaimTimestamp()`:

```
$payload
    ->setClaimTimestamp('iat', time())
    ->setClaimTimestamp('exp', time() + 900);
```

---

### 3) Create &amp; Serialize a Token

[](#3-create--serialize-a-token)

```
use Phithi92\JsonWebToken\Token\Factory\JwtTokenServiceFactory;
use Phithi92\JsonWebToken\Token\Validator\JwtValidator;

$service   = JwtTokenServiceFactory::createDefault();
$validator = new JwtValidator();

$token = $service->createTokenString(
    algorithm: 'RS256',
    manager: $manager,
    payload: $payload,
    validator: $validator,
    kid: 'main-key'
);
```

You may also issue tokens directly from an array of claims:

```
$bundle = $service->createTokenFromArray(
    algorithm: 'RS256',
    manager: $manager,
    claims: ['iss' => 'https://issuer.example', 'exp' => time() + 900],
    validator: $validator,
    kid: 'main-key'
);
```

---

### 4) Read (verify/decrypt) &amp; Validate

[](#4-read-verifydecrypt--validate)

```
$bundle = $service->decryptToken(
    token: $token,
    manager: $manager,
    validator: $validator
);

$payload = $bundle->getPayload();
```

For JWS tokens this verifies the signature and reads the payload. For JWE tokens this decrypts and then validates claims.

#### Claim-Only Validation

[](#claim-only-validation)

```
$isValid = $service->validateTokenClaims(
    bundle: $bundle,
    validator: $validator
);
```

> ⚠️ `*WithoutClaimValidation()` methods exist **only** for tests or tooling.

### 5) Business Rules &amp; Replay Protection

[](#5-business-rules--replay-protection)

`JwtValidator` can enforce issuer, audience, private claims **and** protect against JWT replay attacks via a pluggable JWT ID registry.

### `jti` (JWT ID) Deep Dive

[](#jti-jwt-id-deep-dive)

`jti` is the token identifier claim used to uniquely track a token and support replay prevention.

#### Validation behavior

[](#validation-behavior)

- Without a `JwtIdValidatorInterface`, `jti` is optional and not checked.
- If a `JwtIdValidatorInterface` is configured, tokens **must** contain `jti`.
- The validator then checks whether the `jti` is allowed by the configured backend (in-memory, Redis, PDO).

#### Auto-generation behavior during issuing

[](#auto-generation-behavior-during-issuing)

When issuing via `JwtTokenService::createToken()` / `createTokenFromArray()` and the chosen validator has a JTI validator configured:

- if `jti` is missing, a new random `jti` is generated automatically
- this generated `jti` is pre-registered as allowed
- if `exp` is missing in that situation, issuing fails (because JTI tracking needs expiry context)

Practical recommendation:

- Set `jti` and `exp` explicitly for all tokens that should be replay-protected.
- Use `denyBundle()` after successful one-time use to invalidate the token ID for the remaining token lifetime.

#### InMemoryJwtIdValidator

[](#inmemoryjwtidvalidator)

`InMemoryJwtIdValidator` is a simple, deterministic implementation intended for tests, demos, and short‑lived processes.

```
use Phithi92\JsonWebToken\Token\Validator\InMemoryJwtIdValidator;
use Phithi92\JsonWebToken\Token\Validator\JwtValidator;

$jwtIdValidator = new InMemoryJwtIdValidator(
    allowList: ['token-123'],
    denyList: ['revoked-token'],
    useAllowList: true
);

$validator = new JwtValidator(
    expectedIssuer: 'https://issuer.example',
    expectedAudience: 'https://service.example',
    jwtIdValidator: $jwtIdValidator
);
```

##### How `useAllowList` works

[](#how-useallowlist-works)

- **`useAllowList = true`**
    Only JWT IDs present in `allowList` are accepted.
    Useful for **single‑use tokens**, login flows, or explicit grants.
- **`useAllowList = false` (default)**
    All JWT IDs are accepted **unless** they appear in `denyList`.
    Suitable for classic access tokens with revocation support.

When a token is successfully validated, the service can deny its JWT ID to prevent replay:

```
$service->denyBundle($bundle, $validator);
```

> ⚠️ `InMemoryJwtIdValidator` is process‑local and non‑persistent.
> Use Redis or PDO validators for production replay protection.

#### Storage backends for JTI replay protection

[](#storage-backends-for-jti-replay-protection)

- `InMemoryJwtIdValidator`: ideal for tests and local demos; state is process-local.
- `RedisJwtIdValidator`: distributed runtime deny/allow lists with TTL support.
- `PdoJwtIdValidator`: relational persistence (requires `jwt_id_list` table with expiry column).

### 6) Refresh / Reissue Tokens

[](#6-refresh--reissue-tokens)

```
$newBundle = $service->reissueBundle(
    interval: '+30 minutes',
    bundle: $bundle,
    manager: $manager,
    validator: $validator
);
```

The original bundle remains untouched.

---

Error Handling Patterns
-----------------------

[](#error-handling-patterns)

When issuing, parsing, decrypting, or validating tokens, prefer catching **specific exception types first** and only then falling back to a generic handler.

```
use Phithi92\JsonWebToken\Exceptions\Token\InvalidTokenException;
use Phithi92\JsonWebToken\Exceptions\Token\MalformedTokenException;
use Phithi92\JsonWebToken\Exceptions\Token\UnsupportedTokenTypeException;
use Phithi92\JsonWebToken\Exceptions\Payload\ExpiredPayloadException;
use Phithi92\JsonWebToken\Exceptions\Payload\NotYetValidException;
use Phithi92\JsonWebToken\Exceptions\Payload\InvalidIssuerException;
use Phithi92\JsonWebToken\Exceptions\Payload\InvalidAudienceException;
use Phithi92\JsonWebToken\Exceptions\Crypto\SignatureVerificationException;
use Phithi92\JsonWebToken\Exceptions\Crypto\DecryptionException;
use Phithi92\JsonWebToken\Exceptions\Security\PassphraseNotFoundException;

try {
    $bundle = $service->decryptToken(
        token: $token,
        manager: $manager,
        validator: $validator
    );

    // Optional extra claim validation step
    $service->validateTokenClaims($bundle, $validator);

} catch (ExpiredPayloadException|NotYetValidException $e) {
    // 401: token is time-invalid (expired or not active yet)
} catch (InvalidIssuerException|InvalidAudienceException $e) {
    // 403: token is valid but not intended for this API/context
} catch (SignatureVerificationException|DecryptionException $e) {
    // 401: signature/JWE auth check failed
} catch (MalformedTokenException|InvalidTokenException|UnsupportedTokenTypeException $e) {
    // 400: structurally invalid or unsupported token
} catch (PassphraseNotFoundException $e) {
    // 500: server-side key configuration problem
}
```

### Recommended Mapping (API-friendly)

[](#recommended-mapping-api-friendly)

- **400 Bad Request**: malformed token, missing parts, unsupported format/type
- **401 Unauthorized**: invalid signature, failed decryption/auth tag, expired or not-yet-valid token
- **403 Forbidden**: issuer/audience/private-claim mismatch
- **500 Internal Server Error**: missing key material, passphrase, or other server misconfiguration

### Security Best Practices for Error Responses

[](#security-best-practices-for-error-responses)

- Return a **generic client message** (e.g. `"Invalid or expired token"`) to avoid leaking verification details.
- Log the exact exception message internally with request correlation IDs.
- Do not include secrets, raw token content, or key identifiers in public error payloads unless required.

---

Supported Algorithms
--------------------

[](#supported-algorithms)

Identifiers map to handlers via `resources/algorithms.php`.

### JWS (signing)

[](#jws-signing)

- **HMAC:** `HS256` · `HS384` · `HS512`
- **RSA:** `RS256` · `RS384` · `RS512`
- **RSA-PSS:** `PS256` · `PS384` · `PS512`
- **ECDSA:** `ES256` · `ES384` · `ES512`

### JWE (encryption)

[](#jwe-encryption)

- **RSA-OAEP + AES-GCM:** `RSA-OAEP/A256GCM` · `RSA-OAEP-256/A256GCM`
- **Direct AES-GCM:** `A128GCM` · `A192GCM` · `A256GCM`

> Prefer **RSA-PSS** for new RSA signatures and **AES-GCM** for authenticated encryption. Pin algorithms per client.

---

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

[](#security-best-practices)

- 🔑 Never commit keys or passphrases
- 🔒 Always validate issuer &amp; audience
- ⏱ Use short expiration windows
- 📌 Pin algorithms and `kid`s per client
- 🧯 Catch domain-specific exceptions only

---

Development
-----------

[](#development)

```
composer install
composer run keys   # generate test keys
composer run test
composer run analyse
```

---

License
-------

[](#license)

Released under the MIT License. See [LICENSE](LICENSE).

Support the project
-------------------

[](#support-the-project)

[![ko-fi](https://camo.githubusercontent.com/201ef269611db7eb6b5d08e9f756ab8980df3014b64492770bdf13a6ed924641/68747470733a2f2f6b6f2d66692e636f6d2f696d672f676974687562627574746f6e5f736d2e737667)](https://ko-fi.com/R6R414XGWN)[![image](https://camo.githubusercontent.com/090bbb0665fc4a348a59892c47b2ce64c732c565c5b188fd88684ca175d91a00/68747470733a2f2f73746f726167652e6b6f2d66692e636f6d2f63646e2f7573657275706c6f6164732f5236523431345847574e2f7172636f64652e706e673f763d34306465653036392d323331362d343632662d386333662d3239383235653030666131303f763d32)](https://camo.githubusercontent.com/090bbb0665fc4a348a59892c47b2ce64c732c565c5b188fd88684ca175d91a00/68747470733a2f2f73746f726167652e6b6f2d66692e636f6d2f63646e2f7573657275706c6f6164732f5236523431345847574e2f7172636f64652e706e673f763d34306465653036392d323331362d343632662d386333662d3239383235653030666131303f763d32)

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance87

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity60

Established project with proven stability

 Bus Factor1

Top contributor holds 100% 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 ~35 days

Recently: every ~24 days

Total

15

Last Release

78d ago

Major Versions

0.2.8 → v1.0.02025-11-21

v1.0.1 → v2.0.02026-02-22

PHP version history (2 changes)0.1.0PHP ^8.1

0.2.3PHP &gt;=8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/e802ef166b62f896683c0baca456fb09ee077393e6e319e882bb60c3a1ad5925?d=identicon)[phithi92](/maintainers/phithi92)

---

Top Contributors

[![phithi92](https://avatars.githubusercontent.com/u/21245469?v=4)](https://github.com/phithi92 "phithi92 (458 commits)")

---

Tags

json-web-tokenjwt-tokenphp-library

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/phithi92-json-web-token/health.svg)

```
[![Health](https://phpackages.com/badges/phithi92-json-web-token/health.svg)](https://phpackages.com/packages/phithi92-json-web-token)
```

###  Alternatives

[jumbojett/openid-connect-php

Bare-bones OpenID Connect client

7169.8M38](/packages/jumbojett-openid-connect-php)[azimolabs/apple-sign-in-php-sdk

Library to verify and validate Apple IdentityToken and authenticate a user with Apple ID.

92463.9k](/packages/azimolabs-apple-sign-in-php-sdk)[strobotti/php-jwk

A small PHP library to handle JWKs (Json Web Keys)

24880.8k7](/packages/strobotti-php-jwk)[clerkinc/backend-php

2755.0k](/packages/clerkinc-backend-php)[jakub-onderka/openid-connect-php

Bare-bones OpenID Connect client

1151.4k](/packages/jakub-onderka-openid-connect-php)[laranex/laravel-biometric-auth

A laravel package to provide asymmetric biometric authentication

106.0k](/packages/laranex-laravel-biometric-auth)

PHPackages © 2026

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