Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1053,8 +1053,8 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| `queue-11` | `queue` | `events`, `queue`, and `broadcasting` (revalidation complete) | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `queue-11` |
| `queue-12` | `bus`, `queue` | `events`, `bus`, `queue`, and `broadcasting` (revalidation complete) | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `queue-12` |
| `foundation-01` | `foundation` | `support` and `foundation` (revalidation complete) | `Correct event dispatch, queued-consumer isolation, and queue interoperability`; finding `foundation-01` |
| `support-02` | `support` | `auth` (revalidation complete), `broadcasting` (revalidation complete), `bus` (revalidation complete), `cache` (revalidation complete), `concurrency`, `console` (revalidation complete), `container`, `contracts`, `cookie`, `database` (revalidation complete), `events`, `filesystem` (revalidation complete), `foundation` (revalidation complete), `hashing` (revalidation complete), `horizon` (revalidation complete), `inertia` (revalidation complete), `jwt`, `log`, `mail`, `notifications` (revalidation complete), `permission` (revalidation complete), `pipeline`, `queue` (revalidation complete), `redis` (revalidation complete), `reverb` (revalidation complete), `routing` (revalidation complete), `sanctum` (revalidation complete), `scout`, `session` (revalidation complete), `socialite` (revalidation complete), `telescope`, `testbench`; `translation` (revalidation complete); later full remaining consumer audits | `Normalize framework enum identifiers at string boundaries`; finding `support-02`; sibling findings `translation-01` and `reverb-03`; linked detail plan `2026-07-15-0920-framework-enum-identifier-contracts.md` |
| `macroable-03` | `macroable` | `cookie`, `log`, and `notifications` (revalidation complete); later full `jwt` audit | `Complete Macroable callable and test-state handling`; finding `macroable-03` |
| `support-02` | `support` | `auth` (revalidation complete), `broadcasting` (revalidation complete), `bus` (revalidation complete), `cache` (revalidation complete), `concurrency`, `console` (revalidation complete), `container`, `contracts`, `cookie`, `database` (revalidation complete), `events`, `filesystem` (revalidation complete), `foundation` (revalidation complete), `hashing` (revalidation complete), `horizon` (revalidation complete), `inertia` (revalidation complete), `jwt` (revalidation complete), `log`, `mail`, `notifications` (revalidation complete), `permission` (revalidation complete), `pipeline`, `queue` (revalidation complete), `redis` (revalidation complete), `reverb` (revalidation complete), `routing` (revalidation complete), `sanctum` (revalidation complete), `scout`, `session` (revalidation complete), `socialite` (revalidation complete), `telescope`, `testbench`; `translation` (revalidation complete); later full remaining consumer audits | `Normalize framework enum identifiers at string boundaries`; finding `support-02`; sibling findings `translation-01` and `reverb-03`; linked detail plan `2026-07-15-0920-framework-enum-identifier-contracts.md` |
| `macroable-03` | `macroable` | `cookie`, `jwt`, `log`, and `notifications` (revalidation complete) | `Complete Macroable callable and test-state handling`; finding `macroable-03` |
| `auth-01` | `support`, `auth` | `auth` (revalidation complete) | `Correct Support utility boundaries and authentication timing isolation`; finding `auth-01` |
| `encryption-03` | `encryption` | `contracts`, `support`, `filesystem`, and `foundation` (revalidation complete) | `Harden encryption rotation, key publication, and global lifecycle state`; finding `encryption-03` |
| `sanctum-01` | `sanctum` | `encryption` and `sanctum` (revalidation complete) | `Harden encryption rotation, key publication, and global lifecycle state`; finding `sanctum-01` |
Expand Down Expand Up @@ -1342,7 +1342,7 @@ The order is lower-level first where practical. Hypervel has cross-cutting depen
- [x] `fortify`
- [x] `passkeys`
- [ ] `permission`
- [ ] `jwt`
- [x] `jwt`
- [x] `scout`
- [ ] `telescope`
- [ ] `sentry`
Expand Down

Large diffs are not rendered by default.

400 changes: 400 additions & 0 deletions docs/plans/2026-08-08-0426-jwt-correctness-security-and-lifecycle.md

Large diffs are not rendered by default.

51 changes: 41 additions & 10 deletions src/boost/docs/jwt.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [Configuring the Guard](#configuring-the-guard)
- [User Models](#user-models)
- [Signing Keys and Algorithms](#signing-keys-and-algorithms)
- [Custom Drivers](#custom-drivers)
- [Token Lifetime](#token-lifetime)
- [Subject Locking](#subject-locking)
- [Token Sources](#token-sources)
Expand All @@ -22,7 +23,7 @@
- [Logging Out and Invalidating Tokens](#logging-out-and-invalidating-tokens)
- [Guard Methods](#guard-methods)
- [Exceptions](#exceptions)
- [Differences From "php-open-source-saver/jwt-auth"](#differences-from-php-open-source-saver-jwt-auth)
- [Differences From php-open-source-saver/jwt-auth](#differences-from-php-open-source-saver-jwt-auth)

<a name="introduction"></a>
## Introduction
Expand Down Expand Up @@ -91,6 +92,8 @@ php artisan jwt:generate-certs --force --algo=rsa --bits=4096 --sha=512
php artisan jwt:generate-certs --force --algo=ec --curve=prime256v1 --sha=256
```

RSA keys must be at least 2048 bits.

You may change the output directory using `--dir`. The directory may be absolute or relative to your application's base path.

You may protect the private key with a passphrase using `--passphrase`, or prompt for it interactively using `--ask-passphrase`:
Expand Down Expand Up @@ -186,7 +189,30 @@ For RSA and EC algorithms, configure `JWT_PRIVATE_KEY`, `JWT_PUBLIC_KEY`, and `J
],
```

The key values may be key contents or `file://` paths.
The key values may be key contents or a `file://` URI.

<a name="custom-drivers"></a>
### Custom Drivers

Custom JWT providers must implement the `Hypervel\Jwt\Contracts\ProviderContract` contract, which defines the `encode` and `decode` methods.

You may register a custom JWT provider using the `extend` method. This is typically done in the `boot` method of a service provider:

```php
use App\Jwt\CustomJwtProvider;
use Hypervel\Support\Facades\Jwt;

public function boot(): void
{
Jwt::extend('custom', fn ($app) => $app->make(CustomJwtProvider::class));
}
```

After registering the driver, you may select it using the `driver` configuration option:

```php
'driver' => 'custom',
```

<a name="token-lifetime"></a>
### Token Lifetime
Expand Down Expand Up @@ -338,6 +364,8 @@ The blacklist uses the configured storage provider:

The default tagged-cache storage requires your default cache store to support tags. Both all-mode and any-mode tagged stores are supported. When using any-mode tags, blacklist entries are written through tags but read and removed by a private plain-key prefix.

If your cache store does not support tags, implement `Hypervel\Jwt\Contracts\StorageContract` and configure your implementation using `jwt.providers.storage`.

If the blacklist store uses a cache stack or any node-local tier, a revoked token may still validate on another node until that node's local cache entry expires. Keep the upper-tier TTL short, or use a fully shared store such as Redis when revocation must be visible immediately across all nodes.

You may configure a grace period for concurrent requests that are using the same token while a refresh is in progress:
Expand All @@ -346,10 +374,10 @@ You may configure a grace period for concurrent requests that are using the same
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0),
```

The `blacklist_refresh_ttl` option keeps blacklist entries long enough to cover the token's refresh window:
The `refresh_ttl` option also controls how long blacklist entries are retained. When the refresh lifetime is `null`, revocations for refreshable tokens are retained forever:

```php
'blacklist_refresh_ttl' => env('JWT_BLACKLIST_REFRESH_TTL', 20160),
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),
```

<a name="authenticating-requests"></a>
Expand Down Expand Up @@ -436,13 +464,15 @@ $newToken = Auth::guard('api')->refresh();
Expose refresh through a dedicated endpoint:

```php
use Hypervel\Jwt\Exceptions\JwtException;
use Hypervel\Jwt\Exceptions\TokenBlacklistedException;
use Hypervel\Jwt\Exceptions\TokenExpiredException;
use Hypervel\Jwt\Exceptions\TokenInvalidException;
use Hypervel\Support\Facades\Auth;

Route::post('/token/refresh', function () {
try {
$token = Auth::guard('api')->refresh();
} catch (JwtException) {
} catch (TokenInvalidException|TokenExpiredException|TokenBlacklistedException) {
abort(401, 'Token cannot be refreshed.');
}

Expand Down Expand Up @@ -491,19 +521,21 @@ Managed claims such as `nbf`, `exp`, `iss`, and `jti` are rebuilt by the package
<a name="logging-out-and-invalidating-tokens"></a>
### Logging Out and Invalidating Tokens

The `logout` method clears the current guard user and token. If blacklist is enabled, it also invalidates the current token:
The `logout` method clears the guard's user, token, and decoded payload. When blacklisting is enabled, it invalidates the current token first:

```php
Auth::guard('api')->logout();
```

If the blacklist write fails, a `JwtException` is thrown. The guard keeps its current state and does not dispatch the `Logout` event.

To invalidate a token directly, enable the blacklist and call `invalidate`:

```php
Auth::guard('api')->invalidate();
```

You may pass `true` to blacklist the token forever:
You may pass `true` to blacklist the token forever. This also bypasses the configured grace period, so the revocation takes effect immediately:

```php
Auth::guard('api')->invalidate(true);
Expand Down Expand Up @@ -556,15 +588,14 @@ Common exceptions include:
</div>

<a name="differences-from-php-open-source-saver-jwt-auth"></a>
## Differences From "php-open-source-saver/jwt-auth"
## Differences From php-open-source-saver/jwt-auth

Hypervel JWT is based on `php-open-source-saver/jwt-auth`, but its internals are adapted for Hypervel:

<div class="content-list" markdown="1">

- Hypervel uses array payloads instead of upstream `Payload`, `Token`, and claim DTO objects.
- Hypervel keeps the `Jwt` facade mapped to the array-based `JwtManager`, but does not include upstream `JwtAuth`, `JwtFactory`, or `JwtProvider` facades.
- The parser chain is stateless. Request instances are passed to the parser for each parse so coroutine requests cannot leak through singleton services.
- Cookie token parsing is available but not enabled by default.
- Upstream route-parameter and Lumen parser shortcuts are not included.
- Upstream sliding refresh middleware is not included; use an explicit refresh endpoint that calls `Auth::guard(...)->refresh()`.
Expand Down
11 changes: 3 additions & 8 deletions src/jwt/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
JWT for Hypervel
===

[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/hypervel/jwt)
Documentation: https://hypervel.org/docs/jwt

Ported from: https://github.com/PHP-Open-Source-Saver/jwt-auth

This package provides stateless JWT authentication for Hypervel applications, adapted for long-lived Swoole workers and coroutine-safe request state.

## Differences From "php-open-source-saver/jwt-auth"
## Differences From php-open-source-saver/jwt-auth

- Hypervel uses array payloads instead of upstream `Payload`, `Token`, and claim DTO objects.
- Hypervel keeps the `Jwt` facade mapped to the array-based `JwtManager`, but does not include upstream `JwtAuth`, `JwtFactory`, or `JwtProvider` facades.
- Hypervel's parser chain is stateless and receives the request for each parse so coroutine requests cannot leak through singleton services.
- Cookie token parsing is available but not enabled by default.
- Upstream route-parameter and Lumen parser shortcuts are not included.
- Upstream sliding refresh middleware is not included; use an explicit refresh endpoint that calls `Auth::guard(...)->refresh()`.
- Namshi and Lumen integrations are not included.
- The `show_black_list_exception` option is not included; JWT exceptions fail normally.

Full usage docs are available in `src/boost/docs/jwt.md`.
Ported from: https://github.com/PHP-Open-Source-Saver/jwt-auth
2 changes: 1 addition & 1 deletion src/jwt/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
"php": "^8.4",
"lcobucci/jwt": "^5.0",
"nesbot/carbon": "^3.13.1",
"psr/simple-cache": "^3.0",
"hypervel/auth": "^0.4",
"hypervel/cache": "^0.4",
"hypervel/collections": "^0.4",
"hypervel/config": "^0.4",
"hypervel/console": "^0.4",
"hypervel/context": "^0.4",
"hypervel/contracts": "^0.4",
"hypervel/filesystem": "^0.4",
"hypervel/http": "^0.4",
"hypervel/macroable": "^0.4",
"hypervel/support": "^0.4",
Expand Down
18 changes: 5 additions & 13 deletions src/jwt/config/jwt.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
| Public Key
|--------------------------------------------------------------------------
|
| A path or resource to your public key.
| The public key contents or a file:// URI.
|
| E.g. 'file://path/to/public/key'
|
Expand All @@ -70,7 +70,7 @@
| Private Key
|--------------------------------------------------------------------------
|
| A path or resource to your private key.
| The private key contents or a file:// URI.
|
| E.g. 'file://path/to/private/key'
|
Expand Down Expand Up @@ -118,6 +118,9 @@
| the original token being created until they must re-authenticate.
| Defaults to 2 weeks.
|
| This value also determines how long blacklist entries for refreshable
| tokens are retained. A null value retains those entries forever.
|
| You can also set this to null, to yield an infinite refresh time.
| Some may want this instead of never expiring tokens for e.g. a mobile app.
| This is not particularly recommended, so make sure you have appropriate
Expand Down Expand Up @@ -287,17 +290,6 @@

'blacklist_grace_period' => (int) env('JWT_BLACKLIST_GRACE_PERIOD', 0),

/*
| -------------------------------------------------------------------------
| Refresh time to live of blacklist
| -------------------------------------------------------------------------
|
| Number of minutes from issue date in which a JWT can be refreshed.
|
*/

'blacklist_refresh_ttl' => (int) env('JWT_BLACKLIST_REFRESH_TTL', 20160),

/*
|--------------------------------------------------------------------------
| Providers
Expand Down
Loading
Loading