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

Jose Laravel Package

spomky-labs/jose

spomky-labs/jose is a PHP library for JSON Object Signing and Encryption, offering JWS, JWE, JWK and JWT support. It helps developers sign, encrypt, verify and manage keys for secure tokens and payloads in web applications and APIs.

Deep Wiki
Context7

Getting Started

spomky-labs/jose is a full JOSE stack for PHP — JWS (signing), JWE (encryption), JWK/JWKSet (keys), JWA (algorithms) and JWT on top. It is archived (last release May 2018), so treat it as a legacy-maintenance dependency, not a greenfield choice. If you are starting fresh, use its direct successor web-token/jwt-framework (same author, same object model) or lcobucci/jwt; the patterns below transfer almost 1:1.

Install and pin

composer require spomky-labs/jose:^3.0

The package targets PHP 7.x. On PHP 8+ you will fight composer install unless you --ignore-platform-reqs, and even then the pure-PHP EC backend (mdanter/ecc) has known issues — prefer RSA (ext-openssl) or HMAC.

Where to look first

Namespace What lives there
Jose\Factory\ JWKFactory, JWSFactory, JWEFactory — your entry points
Jose\Object\ JWKInterface, JWKSetInterface, JWSInterface, JWEInterface — type-hint these, not concretes
Jose\ Signer, Verifier, Encrypter, Decrypter, Loader
Jose\Checker\ Claim validators (ExpirationTimeChecker, NotBeforeChecker, …)

Ignore the README's crypto-primer sections on first read; go straight to Factory\ and the Signer/Verifier pair.

First use case: sign and verify an HS256 token

use Jose\Factory\JWKFactory;
use Jose\Factory\JWSFactory;
use Jose\Signer;
use Jose\Verifier;
use Jose\Loader;
use Jose\Checker\ExpirationTimeChecker;
use Jose\Checker\NotBeforeChecker;

// 1. Key
$jwk = JWKFactory::createFromSecret('a-long-shared-secret', [
    'alg' => 'HS256',
    'use' => 'sig',
]);

// 2. Build and sign
$jws = JWSFactory::createJWSWithClaims([
    'iss' => 'https://app.example.com',
    'sub' => 'user-42',
    'iat' => time(),
    'exp' => time() + 3600,
]);
$jws->addSignature($jwk, ['alg' => 'HS256']);

Signer::createSigner(['HS256'])->sign($jws);

$token = $jws->toJSON(); // see Gotchas: this is the JSON serialization

// 3. Load and verify
$loaded = (new Loader())->load($token);

$verifier = Verifier::createVerifier(
    ['HS256'],                                   // algorithm allowlist — never wildcard
    [new ExpirationTimeChecker(), new NotBeforeChecker()]
);
$verifier->verify($loaded, $jwk);
$claims = $loaded->getPayload();

If that round-trip works, everything else in the package is a variation on key type, algorithm, and claim checks.

Implementation Patterns

Wrap it in one service class, immediately. The package has no Laravel service provider. Do not sprinkle Signer::createSigner() through controllers — you will be migrating off this library, and the successor's API is similar but not identical. One seam makes that a one-file change.

// app/Support/Jwt.php
final class Jwt
{
    public function __construct(
        private JWKInterface $signingKey,
        private JWKSetInterface $verificationKeys,
    ) {}

    public function issue(array $claims): string
    {
        $jws = JWSFactory::createJWSWithClaims($claims + ['iat' => time()]);
        $jws->addSignature($this->signingKey, [
            'alg' => 'RS256',
            'kid' => $this->signingKey->get('kid'),
        ]);
        Signer::createSigner(['RS256'])->sign($jws);

        return $jws->toJSON();
    }

    public function parse(string $token): array
    {
        $jws = (new Loader())->load($token);

        Verifier::createVerifier(['RS256'], [
            new ExpirationTimeChecker(),
            new NotBeforeChecker(),
        ])->verify($jws, $this->verificationKeys);

        return $jws->getPayload();
    }
}

Bind it as a singleton in a service provider; Signer/Verifier are cheap and stateless, but the JWK objects are worth constructing once.

Stateless API auth middleware.

public function handle(Request $request, Closure $next)
{
    $claims = app(Jwt::class)->parse($request->bearerToken());

    $request->attributes->set('jwt', $claims);
    Auth::setUser(new \App\Models\User((string) $claims['sub']));

    return $next($request);
}

Prefer $request->attributes over Auth::setUser if you want the token usable outside a Laravel auth guard (queues, artisan commands).

OIDC / third-party token validation via JWKS. JWKFactory::createFromJKU() performs a live HTTP fetch. There is no caching inside the package, so cache the serialized set and rehydrate.

$jwkset = Cache::remember('oidc:jwks', now()->addHour(), function () {
    return JWKFactory::createFromJKU('https://idp.example.com/.well-known/jwks.json');
});

Rehydrate from cache with new JWKSet(json_decode($cached, true)) rather than storing the object. On an unknown kid (key rotation at the IdP), bust the cache and refetch once before failing.

Key rotation with kid. Keep an array of JWKs in a JWKSet, tag each with a distinct kid, and sign with the active one. The Verifier matches against the whole set, so old tokens stay valid through a rotation window — this is the main reason to use a JOSE library rather than hash_hmac by hand.

Custom claim checks as checkers. Jose\Checker is the intended extension point. Implement the checker interface to enforce scope, tenant, or aud in the same pass as exp, instead of forgetting them in application code.

Nested JWTs for sensitive payloads. Sign with JWSFactory, then encrypt the resulting token with Encrypter::createEncrypter(['RSA-OAEP'], ['A256GCM']). Decrypt first, then verify — never the reverse, or you are verifying attacker-controlled ciphertext.

Testing. Generate a throwaway key in setUp() with JWKFactory::createFromSecret() or a fixture PEM; never commit production keys or make test suites depend on a network JWKS fetch.

Gotchas and Tips

  • toJSON() is not compact serialization. It returns the general JSON form ({"payload":…,"signatures":[…]}), not the xxx.yyy.zzz string most clients and Authorization: Bearer headers expect. This is the single most common first-hour bug. Check the API of the tag you pinned for the compact accessor, or serialize the detatched form yourself.
  • exp is not enforced by default. The Verifier only runs the checkers you hand it. A token with exp in the past verifies fine if you omit ExpirationTimeChecker. Same for nbf, iat, aud and iss — build the checker list explicitly, and add audience/issuer checks yourself if the bundled Checker namespace doesn't cover them.
  • Never widen the algorithm allowlist. Verifier::createVerifier([$tokenAlg], …) reading the algorithm from the token is the classic alg: none / HS256-vs-RS256 confusion attack. Hardcode the expected algorithm(s) at the call site.
  • Don't trust kid to select keys. Match kid against your known set; a kid pointing at an attacker-supplied JWK (especially via jku/x5u headers) is a key-injection vector. Ignore jku/x5u entirely unless you have a strict host allowlist.
  • API drift between 2.x and 3.x is real. Method names around payload/claims access (getPayload() vs. claim accessors) and signature handling changed. Pin an exact minor version and verify sample code against the tag you installed, not against blog posts.
  • JWKFactory::createFromJKU() has no timeout or retry. A slow IdP blocks your request thread indefinitely. Wrap it, or fetch JWKS out-of-band in a scheduled job.
  • Base64url, not base64. If you hand-build a JWK array, k, n, e, d must be base64url-encoded (no padding). createFromSecret() and createFromKeyFile() handle this; raw arrays from a config file usually don't.
  • ECDSA is slow and fragile. mdanter/ecc is pure PHP. Unless you specifically need small signature sizes or are targeting JOSE conformance, RS256 is faster and better supported on PHP 7/8.
  • Exception messages are generic. "Unable to verify the signature" covers a wrong key, unsupported alg, malformed base64 and clock skew alike. Log $e->getPrevious(), and dump $jws->getSignatures()[0]->getProtectedHeaders() when debugging — the headers usually reveal the mismatch.
  • No security patching. The archive date means no CVE fixes in this package or, transitively, in its pinned crypto deps. Run composer audit on the lockfile and budget the migration to web-token/jwt-framework — the object model (JWKSet → JWKFactory → JWSFactory → Signer/Verifier) is close enough that the service-class wrapper above can be rewritten in an afternoon.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager