facile-it/php-jose-verifier
A lightweight PHP library for validating JWT tokens from OpenID and OAuth2 providers. It builds verifiers and decrypters automatically from issuer and client metadata, fetching public keys from a jwks_uri endpoint, with optional GMP support for better performance.
Architecture fit
facile-it/php-jose-verifier is a framework-agnostic PHP library — no service provider, no facade, no config file, no Laravel contracts. It occupies a specific niche in a Laravel stack: relying-party / resource-server verification of JWTs issued by an external OIDC provider (Keycloak, Auth0, Okta, Entra ID, Cognito). It complements rather than competes with Laravel Passport (which issues its own tokens) and tymon/jwt-auth (self-issued, self-verified tokens). If our product consumes tokens minted by a third-party IdP, this library is a strong fit; if we mint and verify our own tokens, it is the wrong tool.
The API surface is small and stable in shape: three builder/verifier pairs (AccessToken, IdToken, UserInfo), each created from issuer metadata (issuer, jwks_uri) plus client metadata (client_id, optional client_secret, response algorithms). That maps cleanly onto a config/oidc.php file and container singletons — one verifier instance per configured issuer. Builders and verifiers are immutable, which is a genuine architectural asset for Octane/Swoole workers where shared mutable state is a correctness hazard.
Integration feasibility
Feasible with roughly 1–2 days of engineering for a production-quality wrapper — not turnkey, but not a project. Three concrete friction points:
Psr\SimpleCache\CacheInterface, which Laravel's Cache facade does not implement. Without an adapter (Symfony cache bridge or ~50 lines of custom code), every request refetches the remote JWKS — a latency and availability problem, since it puts the IdP's JWKS endpoint on the critical path of every authenticated request.Http facade. We either bind Guzzle explicitly or add an adapter, and we lose Laravel's HTTP middleware (retries, structured logging, telemetry) unless we wrap the client. Worth deciding deliberately before the first PR.Technical risk
web-token/* packages were replaced, JWE compression support was dropped. Under 0.x semver, minor bumps may break us. Pin an exact version and treat upgrades as change-managed work with a test pass, not as composer update background noise.web-token/jwt-library. Most future CVEs will land there, not here. Our dependency scanning and patch cadence must target that subtree, and the 1.0.0 upgrade to jwt-library 4.x carries its own documented breaking changes.ext-gmp. README explicitly recommends it; without it, RSA verification takes a slower path. This is a deployment-image checklist item, and it silently degrades rather than failing loudly — so it needs a health check or build-time assertion.Key questions
id_token validation only, or also access_token and signed/encrypted UserInfo responses?Stack fit
The library sits at the authentication boundary of the Laravel app and nothing else. Recommended shape:
config/oidc.php) holding issuer metadata and client metadata per named issuer, sourced from environment variables. Keep client_secret out of the repo and out of the config cache where possible.Guard) that extracts the bearer token, resolves the correct verifier, calls verify(), and translates InvalidTokenExceptionInterface into a 401 with a non-leaky error body.Resist the urge to expose the verifiers directly to controllers. A thin domain-level "authenticated principal" type returned by our own service keeps the library swappable.
Migration path
Greenfield consumption is straightforward: add the dependency, write the wrapper, wire middleware, ship. For teams already using tymon/jwt-auth or Passport for external tokens, migration is not a drop-in — the libraries differ in what they validate (this one validates against remote JWKS and issuer metadata; those validate against a local secret). The realistic migration is: run both implementations in parallel behind a feature flag, shadow-verify a sample of real traffic through the new path, compare accept/reject decisions, then cut over. Budget for this if it is a replacement rather than an addition.
Compatibility
ext-gmp should be a hard requirement in composer.json at the app level, not a soft README recommendation.facile-it/php-jose-verifier exactly and let web-token/jwt-library float within a constrained range, or pin both. Choose deliberately — pinning both maximizes reproducibility but slows security patching.Sequencing
ext-gmp to all runtime images and CI.Maintenance
Steady-state maintenance is low but non-zero, and it is not zero because of the package's own code — it is low because the package's code is small. The real maintenance load is: (a) tracking web-token/jwt-library releases and CVEs, (b) absorbing occasional breaking changes on 0.x minor bumps, and (c) maintaining our own wrapper. Assign a named owner. A package with 5 stars and 0 dependents that nobody owns is a package that quietly rots.
Set a review cadence — quarterly is reasonable — and subscribe to GitHub security advisories for both this package and web-token/jwt-library. A composer audit gate in CI is the minimum bar.
Support
There is no commercial support. Upstream responsiveness is unknown and unguaranteed. Our practical support model is: we can read the source (it is small and well-structured), and we can fork it. For a security-critical path, the fork option needs to be plausible rather than theoretical — meaning at least one engineer on the team has read the verification flow and could debug it. If nobody can do that, the risk profile changes materially.
Scaling
The library itself is stateless and CPU-bound; it scales horizontally with the app tier. Two operational dependencies govern real-world scaling:
ext-gmp is a throughput lever. With it, RSA verification is fast enough to be a rounding error; without it, high-RPS services will notice. Validate under expected load before committing.Consider caching verification results per-token (keyed by token hash) within the token's remaining lifetime if a single token is verified repeatedly on hot paths — the library does not do this for you.
Failure modes
kid rather than blindly serving the cached set. Verify this behavior explicitly — it is the single most common production surprise in JWKS-based verification.nbf/exp validation is skew-sensitive. Ensure hosts are NTP-synced; a few seconds of drift produces sporadic, hard-to-reproduce 401s.ext-gmp or a misconfigured algorithm allow-list fails open or fails obscurely depending on configuration. Assert the algorithm allow-list explicitly rather than accepting whatever the token header claims — this is the classic JWT alg: none / algorithm-confusion class of bug.Ramp-up
For a Laravel engineer familiar with OIDC concepts, the library is a half-day read (the README is good, the API is four classes). The longer ramp is on the domain: JWKS, key rotation, at_hash/c_hash validation, auth_time and require_auth_time, signed vs. encrypted UserInfo, and the algorithm allow-list. Budget 2–3 days for an engineer to be genuinely productive, most of it spent on OIDC semantics rather than on this package's API. The absence of first-party Laravel documentation means our wrapper's docs become the team's reference — write them as part of the integration, not after.
How can I help you explore Laravel packages today?