Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Lexik Jose Bridge Laravel Package

north69/lexik-jose-bridge

A bridge integrating Lexik JWT authentication with JOSE-based token handling, letting PHP applications sign, verify, and manage JSON Web Tokens through a clean, interoperable layer built on standard JOSE libraries.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a Symfony bundle, not a Laravel one — plan accordingly. It swaps the JWT signer/verifier that lexik/jwt-authentication-bundle uses by default for the one shipped in web-token/jwt-framework (Spomky-Labs), so you get JOSE-compliant JWS handling, kid-aware key sets, and PS/ES algorithm support inside a normal Lexik login flow.

Minimal steps:

  1. Install both halves of the contract, since the bridge is only glue:
    composer require north69/lexik-jose-bridge \
        web-token/jwt-framework \
        lexik/jwt-authentication-bundle
    
  2. Register the bundle in config/bundles.php (the package name suggests something like North69\LexikJoseBridge\LexikJoseBridge; confirm the FQCN in the repo's composer.json autoload.psr-4 block before trusting it):
    return [
        // ...
        North69\LexikJoseBridge\LexikJoseBridge::class => ['all' => true],
    ];
    
  3. Generate keys (bin/console lexik:jwt:generate-keypair still works for RSA, but web-token prefers JWK — see Gotchas).
  4. Point Lexik at the bridge. Lexik resolves its signer through the lexik_jwt_authentication.jws_provider service alias, so the bridge normally just needs to win that alias. Check whether the bundle does it automatically or whether you must alias it yourself in config/services.yaml:
    lexik_jwt_authentication.jws_provider:
        alias: north69_lexik_jose.jws_provider
    

Where to look first when something is wrong: run bin/console debug:container lexik_jwt_authentication.jws_provider --show-arguments. That single command tells you whether the bridge actually replaced the default provider or whether you are still signing with lcobucci/namshi and only think you migrated.

The first real use case is almost always "we need PS256" or "we need ES256/EdDSA", which Lexik's default provider does not offer. Configure the algorithm on the Lexik side and let the bridge map it to a web-token Algorithm instance:

# config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
    secret_key: '%env(JWT_SECRET_KEY)%'
    public_key: '%env(JWT_PUBLIC_KEY)%'
    encoder:
        signature_algorithm: PS256

If the login endpoint returns a token, jwt.io decodes it, and the alg header reads PS256, the bridge is live.

Implementation Patterns

Service-alias override (the canonical pattern). The bridge exists to satisfy Lexik\Bundle\JWTAuthenticationBundle\Services\JWSProvider\JWSProviderInterface. Every integration ultimately reduces to "make the container resolve that interface to the web-token-backed implementation." Keep the override in one place and add an assertion in CI:

// tests/JwtProviderTest.php
$provider = static::getContainer()->get('lexik_jwt_authentication.jws_provider');
self::assertInstanceOf(North69\LexikJoseBridge\...\JWSProvider::class, $provider);

This catches the silent regression where a composer update of Lexik changes the alias name and your bridge quietly stops being used.

Key rotation via JWK sets. This is the reason to adopt a JOSE bridge at all. Keep a JWKS file with two RSA keys, sign with the newest, and let the verifier accept the previous one during the overlap window:

# config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
    secret_key: '%env(JWT_SECRET_KEY)%'   # PKCS#8 or JWK, depending on bridge config
    public_key: '%env(JWT_PUBLIC_KEY)%'

Then rotate by generating a new keypair, appending the old public key to the verification set, deploying, and dropping the old entry only after the maximum token TTL has elapsed. Every token must carry a kid header for this to work — verify the bridge sets it, because some providers only add kid when given a key set rather than a single JWK.

Asymmetric-only for multi-service. With HS256 the secret is shared with every consumer, which means any consumer can mint tokens. If more than one service validates tokens, configure RS256/PS256 and distribute only the public key (or a JWKS endpoint) to the verifiers. The bridge's value proposition is that this stops being a custom JWSProvider you maintain.

Custom claims and headers. Lexik emits lexik_jwt_authentication.on_jwt_created / on_jwt_decoded events. Put role/permission claims there rather than subclassing the provider — the bridge owns the provider service, and subclassing couples you to its constructor signature:

class AddTenantClaimListener
{
    public function onJwtCreated(JWTCreatedEvent $event): void
    {
        $payload = $event->getData();
        $payload['tenant'] = $event->getUser()->getTenantId();
        $event->setData($payload);
    }
}

Two-token pattern for rotation windows. Issue a long-lived refresh token (opaque, DB-backed) plus a short-lived access token signed by the bridge. Rotating signing keys is trivial when access tokens live 15 minutes; it becomes a coordination problem when they live 24 hours.

Unit-testing the provider directly. Bypass the HTTP layer for signing tests:

$provider = self::getContainer()->get('lexik_jwt_authentication.jws_provider');
$jws = $provider->create(['sub' => 'user-1', 'exp' => time() + 60]);
self::assertTrue($provider->load($jws->getToken())->isVerified());

JWSProviderInterface in Lexik 2.x exposes create(array $payload): JWS, load(string $token): JWS, and verify(string $token): JWS, each returning a JWS value object with getToken(), getPayload(), isVerified(). Assert against those rather than against raw strings.

Gotchas and Tips

Framework mismatch is the headline risk. If your stack is Laravel, this package is not for you — Laravel's JWT story is tymon/jwt-auth or firebase/php-jwt, neither of which consumes a Symfony bundle. The bridge only makes sense inside a Symfony app with Lexik already installed. Treat the "Laravel developer" framing in this task as a trap and check composer.json require before adding it to a Laravel project.

Zero stars, zero dependents is a real signal. This is one person's glue code sitting between two libraries that both churn. Pin the bridge to an exact version in composer.json, and read the whole source — a JOSE bridge is typically 200–400 lines, so an afternoon of reading buys you full understanding of what it does and does not wire up.

Key format friction. web-token/jwt-framework operates on Jose\Component\Core\JWK objects, while Lexik's ecosystem normally deals in PEM file paths. A bridge either accepts PEM paths and converts internally (JWKFactory::createFromKeyFile() with Algorithm/use parameters) or insists on JWK. If you see a JWKFactory error about a missing kty or an unrecognized PEM header, you are passing the wrong format. PKCS#1 (BEGIN RSA PRIVATE KEY) and PKCS#8 (BEGIN PRIVATE KEY) are different; web-token generally wants PKCS#8 or an explicit JWKFactory::createFromKey() with a passphrase.

Algorithm confusion is a CVE class, not a nitpick. A verifier must enforce an allowlist of algorithms and explicitly reject none. If the bridge maps Lexik's signature_algorithm config straight to a web-token Algorithm object without a none guard, you have an auth bypass. Grep the source for how the verifier is constructed and confirm the AlgorithmManager contains only the algorithm(s) you configured — not a dynamic list built from the token header.

kid is often missing. Multi-key verification fails with a confusing "unable to find key" error when the JWS header lacks kid. Verify the produced token's header (base64url-decode the first segment) before building any rotation plan around it.

Clock skew. web-token validates exp, nbf, and iat with zero leeway by default in some configurations. In a container fleet with imperfect NTP, tokens minted on one node get rejected on another. Look for a leeway/Clock option in the bridge config; if it is not exposed, you may need to construct the JWSVerifier yourself.

Base64url, not base64. JOSE strips padding and uses -/_. Any code that inspects the token manually must use base64_decode(strtr($segment, '-_', '+/')). jwt.io and firebase/php-jwt's JWT::decode are your friends for cross-checking.

Debugging checklist, in order:

  1. bin/console debug:container lexik_jwt_authentication.jws_provider --show-arguments — is the bridge actually wired?
  2. Decode the token header — is alg what you configured, is kid present?
  3. bin/console lexik:jwt:check-config if the Lexik version provides it.
  4. Temporarily log inside JWSVerifier failures — web-token throws granular exceptions (InvalidSignatureException, SignatureException) that surface as generic 401s through Lexik.
  5. Confirm security.yaml firewall and access_token_public_key/authenticator config still reference the right key.

Extension point of last resort. If the bridge lags a Lexik or web-token release, implement JWSProviderInterface yourself in ~80 lines against the current web-token API and alias that. You lose the package's maintenance, but you gain control over the exact AlgorithmManager and key resolution — and given the dependency size, that is a reasonable trade for anything auth-critical. Keep it behind the same service alias so swapping back later is a one-line change.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource