PHPackages                             webpatser/resonate-token-auth - 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. webpatser/resonate-token-auth

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

webpatser/resonate-token-auth
=============================

Token-based subscription auth for Resonate: skip /broadcasting/auth for mobile and S2S clients with a JWT

v0.2.0(2mo ago)001MITPHPPHP ^8.5CI passing

Since May 25Pushed 3w agoCompare

[ Source](https://github.com/webpatser/resonate-token-auth)[ Packagist](https://packagist.org/packages/webpatser/resonate-token-auth)[ RSS](/packages/webpatser-resonate-token-auth/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (6)Versions (3)Used By (1)

Resonate Token Auth
===================

[](#resonate-token-auth)

Token-based subscription auth for [Resonate](https://github.com/webpatser/resonate). Skips the `/broadcasting/auth` HMAC round-trip for clients that have a token (JWT by default, anything via a pluggable validator) instead of a session cookie.

The problem it solves
---------------------

[](#the-problem-it-solves)

Resonate authenticates private and presence channel subscriptions the Pusher way: the browser POSTs `socket_id` + `channel` to your Laravel `/broadcasting/auth` endpoint, the endpoint reads the session, and returns an HMAC signature the client carries in `pusher:subscribe`. That works for browsers; it does not work as cleanly for:

- mobile clients that do not carry a session cookie,
- server-to-server bots that need to subscribe,
- federated apps where the user identity lives in an OAuth or JWT issuer.

This package lets a connection present a token directly to Resonate. The plugin validates it, derives the user identity from its claims, and synthesizes the HMAC signature the standard subscribe path already knows how to verify, so the rest of Resonate runs unchanged.

How it works
------------

[](#how-it-works)

### One verify path, two ways to feed it

[](#one-verify-path-two-ways-to-feed-it)

```
client                       TokenAuthPlugin                 Resonate core
──────                       ───────────────                 ─────────────
pusher:subscribe       ─►    validate token  ─►              EventHandler::subscribe
{channel, token}             authorize claim                   verify HMAC (passes)
                             synthesize HMAC                   member_added
                             hand off                          subscription_succeeded

```

When a `pusher:subscribe` arrives with a `token` field instead of an `auth` field, the plugin:

1. validates the token via the configured `TokenAuthenticator` (JWT by default),
2. asks the `ChannelAuthorizer` whether the claims may subscribe to this channel,
3. for presence channels, builds the `channel_data` from the token's `user_id` (and optional `user_info`),
4. computes the same HMAC signature `/broadcasting/auth` would have returned, using the app's secret,
5. hands the rewritten payload to `EventHandler::handle()` so the standard subscribe path runs.

Because the plugin signs with the same secret the verifier checks against, the standard `InteractsWithPrivateChannels::verify()` accepts the synthesized signature without ever knowing the plugin was involved. The `subscription_succeeded` frame, the presence `member_added` broadcast, the plugin lifecycle hooks: all of it runs.

### Per-subscribe or pre-authenticated

[](#per-subscribe-or-pre-authenticated)

The plugin supports two equivalent flows. Pick whichever fits your client.

**Per-subscribe (stateless).** Include the token on every subscribe:

```
{"event": "pusher:subscribe", "data": {"channel": "presence-chat.42", "token": "..."}}
```

**Pre-authenticated (cached).** Authenticate once, then subscribe without a token:

```
{"event": "app:authenticate", "data": {"token": "..."}}
```

The plugin replies with `app:authenticated` and caches the validated claims on the connection. Subsequent subscribes need no token:

```
{"event": "pusher:subscribe", "data": {"channel": "presence-chat.42"}}
```

The two flows coexist. A per-subscribe token always overrides the cached claims, so a stateless client never depends on connection-bound state.

### Coexists with HMAC

[](#coexists-with-hmac)

A `pusher:subscribe` that already carries an `auth` field is relayed untouched: the standard `/broadcasting/auth` flow runs. Browser clients with cookies keep working; token clients use the new path. They share the same server, the same channels, the same presence semantics.

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

[](#installation)

```
composer require webpatser/resonate-token-auth
```

Publish the config if you want to change defaults:

```
php artisan vendor:publish --tag=resonate-token-auth-config
```

Registering the plugin
----------------------

[](#registering-the-plugin)

```
// config/reverb.php
'servers' => [
    'reverb' => [
        // ...
        'plugins' => [
            \Webpatser\ResonateTokenAuth\TokenAuthPlugin::class,
        ],
    ],
],
```

Restart Resonate (`php artisan resonate:start`, or `resonate:reload` for a zero-downtime swap).

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

[](#configuration)

```
RESONATE_TOKEN_AUTH_ALG=HS256
RESONATE_TOKEN_AUTH_SECRET=your-shared-jwt-secret
# RESONATE_TOKEN_AUTH_PUBLIC_KEY (PEM)        # for RS*/ES* algorithms
# RESONATE_TOKEN_AUTH_ISSUER=https://your.app
# RESONATE_TOKEN_AUTH_AUDIENCE=resonate
```

KeyDefaultPurpose`algorithm``HS256`JWT signing algorithm. HS256/384/512 (shared secret) or RS256/384/512, ES256/384 (public key).`secret``null`Shared secret for HS\* algorithms.`public_key``null`PEM-encoded public key for RS\*/ES\* algorithms.`issuer``null`When set, the token's `iss` claim must match.`audience``null`When set, the token's `aud` claim must match (string or list).`leeway``30`Seconds of clock skew tolerated on `exp`/`nbf`/`iat`.`claims.user_id``sub`Which JWT claim is the user id.`claims.user_info``user_info`Which claim holds optional presence user info.`claims.channels``channels`Which claim, if present, lists allowed channels (glob patterns).Plugging in another token format
--------------------------------

[](#plugging-in-another-token-format)

The default validator is JWT. To accept Sanctum tokens, opaque tokens against an introspection endpoint, or anything else, implement `TokenAuthenticator` and bind it:

```
// AppServiceProvider::register()
$this->app->bind(
    \Webpatser\ResonateTokenAuth\Contracts\TokenAuthenticator::class,
    \App\Resonate\SanctumTokenAuthenticator::class,
);
```

`ChannelAuthorizer` is the same: bind your own for tenant rules, role checks, or external lookups.

Issuing tokens
--------------

[](#issuing-tokens)

A short Laravel helper to mint a JWT this plugin accepts (with `firebase/php-jwt`):

```
use Firebase\JWT\JWT;

return JWT::encode([
    'iss' => config('app.url'),
    'aud' => 'resonate',
    'sub' => (string) $user->id,
    'user_info' => ['name' => $user->name],
    'channels' => ['presence-chat.*', 'private-user.'.$user->id],
    'iat' => time(),
    'exp' => time() + 300,
], config('resonate-token-auth.secret'), 'HS256');
```

Mobile clients fetch this on login and pass it in `pusher:subscribe.data.token`.

Security notes
--------------

[](#security-notes)

- **Bearer tokens.** Anyone holding a valid token can subscribe. Serve tokens over TLS, keep `exp` short (minutes, not days), and treat the signing key as a server secret.
- **No revocation.** A valid, unexpired JWT cannot be revoked by this plugin. Use short lifetimes; if revocation matters, bind a custom `TokenAuthenticator` that checks a deny list.
- **One identity per connection.** The first valid `app:authenticate` binds the cached claims; subsequent `app:authenticate` events are ignored so a long-lived connection cannot silently change identity.
- **Public channels are untouched.** The plugin only authorizes `private-*` and `presence-*` subscribes; public channels are relayed.

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

[](#requirements)

- PHP 8.5+
- Resonate 0.4+

Testing
-------

[](#testing)

```
composer test
```

The suite does not need Redis or any external service.

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance92

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 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 ~0 days

Total

2

Last Release

60d ago

### Community

Maintainers

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

---

Top Contributors

[![webpatser](https://avatars.githubusercontent.com/u/25720?v=4)](https://github.com/webpatser "webpatser (3 commits)")

---

Tags

jwtauthWebSocketspusherBroadcastingresonate

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/webpatser-resonate-token-auth/health.svg)

```
[![Health](https://phpackages.com/badges/webpatser-resonate-token-auth/health.svg)](https://phpackages.com/packages/webpatser-resonate-token-auth)
```

###  Alternatives

[laravel/socialite

Laravel wrapper around OAuth 1 &amp; OAuth 2 libraries.

5.7k108.5M925](/packages/laravel-socialite)[php-open-source-saver/jwt-auth

JSON Web Token Authentication for Laravel and Lumen

84611.1M64](/packages/php-open-source-saver-jwt-auth)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[laravel/ai

The official AI SDK for Laravel.

1.0k3.2M246](/packages/laravel-ai)[illuminate/auth

The Illuminate Auth package.

10528.2M1.3k](/packages/illuminate-auth)[illuminate/filesystem

The Illuminate Filesystem package.

15265.2M3.3k](/packages/illuminate-filesystem)

PHPackages © 2026

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