facile-it/php-jose-verifier
Validate and verify JWTs (JOSE) with builder-based verifiers geared for OAuth2/OpenID Connect. Create verifiers from issuer metadata (issuer, jwks_uri) and client metadata (client_id/secret), with optional JWK sets for decryption; ext-gmp recommended for speed.
## Getting Started
### Minimal Setup in Laravel
1. **Install the package**:
```bash
composer require facile-it/php-jose-verifier
Ensure ext-gmp is enabled for performance (recommended).
Fetch issuer metadata (e.g., from /well-known/openid-configuration):
$issuerMetadata = [
'issuer' => 'https://your-oidc-provider.com',
'jwks_uri' => 'https://your-oidc-provider.com/jwks',
];
Define client metadata (from OAuth2 registration):
$clientMetadata = [
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // Optional for symmetric signing
];
Create a verifier (e.g., for access tokens in a Laravel middleware):
use Facile\JoseVerifier\Builder\AccessTokenVerifierBuilder;
$verifier = AccessTokenVerifierBuilder::create($issuerMetadata, $clientMetadata)
->withJwksProviderBuilder((new \Facile\JoseVerifier\JWK\JwksProviderBuilder())
->withCache(app(\Psr\SimpleCache\CacheInterface::class))
->withCacheTtl(86400))
->build();
Validate a token (e.g., in a route or API controller):
try {
$payload = $verifier->verify($request->bearerToken());
// $payload now contains decoded claims (e.g., 'sub', 'exp', 'scope')
} catch (\Facile\JoseVerifier\Exception\InvalidTokenExceptionInterface $e) {
abort(401, 'Invalid token: ' . $e->getMessage());
}
Use Laravel’s middleware to validate tokens across routes:
namespace App\Http\Middleware;
use Closure;
use Facile\JoseVerifier\Exception\InvalidTokenExceptionInterface;
class ValidateJwtToken
{
public function __construct(
protected \Facile\JoseVerifier\AccessTokenVerifierInterface $verifier
) {}
public function handle($request, Closure $next)
{
try {
$payload = $this->verifier->verify($request->bearerToken());
$request->merge(['user' => $payload]); // Attach claims to request
return $next($request);
} catch (InvalidTokenExceptionInterface $e) {
return response()->json(['error' => 'Unauthorized'], 401);
}
}
}
Register in app/Http/Kernel.php:
protected $routeMiddleware = [
'auth.jwt' => \App\Http\Middleware\ValidateJwtToken::class,
];
Centralize verifier creation in a service provider:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Facile\JoseVerifier\Builder\AccessTokenVerifierBuilder;
class JoseVerifierServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(\Facile\JoseVerifier\AccessTokenVerifierInterface::class, function ($app) {
$issuerMetadata = config('oidc.issuer_metadata');
$clientMetadata = config('oidc.client_metadata');
return AccessTokenVerifierBuilder::create($issuerMetadata, $clientMetadata)
->withJwksProviderBuilder((new \Facile\JoseVerifier\JWK\JwksProviderBuilder())
->withCache($app->make(\Psr\SimpleCache\CacheInterface::class)))
->build();
});
}
}
For OpenID Connect flows (e.g., authorization code exchange):
use Facile\JoseVerifier\Builder\IdTokenVerifierBuilder;
$verifier = IdTokenVerifierBuilder::create($issuerMetadata, $clientMetadata)
->build()
->withState($request->session()->get('oauth_state'))
->withAccessToken($accessToken)
->withCode($authorizationCode);
try {
$idTokenPayload = $verifier->verify($idToken);
// Verify `nonce`, `auth_time`, etc.
} catch (InvalidTokenExceptionInterface $e) {
// Handle token errors
}
For JWT-based userinfo responses:
use Facile\JoseVerifier\Builder\UserInfoVerifierBuilder;
$verifier = UserInfoVerifierBuilder::create($issuerMetadata, $clientMetadata)
->build();
try {
$userInfo = $verifier->verify($jwtUserInfoResponse);
// $userInfo contains claims like 'email', 'name', etc.
} catch (InvalidTokenExceptionInterface $e) {
abort(400, 'Invalid userinfo token');
}
Extend verifiers with custom checks (e.g., for custom:role claims):
use Facile\JoseVerifier\Checker\ClaimCheckerInterface;
class RoleChecker implements ClaimCheckerInterface
{
public function check(array $payload, string $claimName): void
{
if ($claimName === 'custom:role' && !in_array($payload['custom:role'], ['admin', 'user'])) {
throw new \RuntimeException('Invalid role');
}
}
}
// Usage:
$builder = AccessTokenVerifierBuilder::create($issuerMetadata, $clientMetadata)
->withCustomCheckers([new RoleChecker()]);
Psr\SimpleCache (e.g., Redis) to cache remote JWKs:
$jwksProviderBuilder = (new \Facile\JoseVerifier\JWK\JwksProviderBuilder())
->withCache(app(\Psr\SimpleCache\CacheInterface::class))
->withCacheTtl(3600); // 1-hour cache
$cache = app(\Psr\SimpleCache\CacheInterface::class);
$cache->set('token:' . $payload['sub'], $payload, 300); // 5-minute cache
Extend Laravel’s auth system to use JWT payloads:
use Illuminate\Contracts\Auth\Authenticatable;
class JwtUser implements Authenticatable
{
public function __construct(private array $payload) {}
public function getAuthIdentifierName(): string
{
return 'sub';
}
public function getAuthIdentifier(): string
{
return $this->payload['sub'];
}
// ... other Authenticatable methods
}
// Usage in middleware:
$payload = $verifier->verify($token);
$user = new JwtUser($payload);
auth()->login($user);
| Issue | Solution |
|---|---|
InvalidTokenException |
Check token format (must be a valid JWT string). Use web-token/jwt-framework to debug. |
| JWK Fetch Failures | Ensure jwks_uri is correct and accessible. Test with curl $jwks_uri. |
| Clock Skew Errors | Use Psr\Clock\ClockInterface (injected by default) to avoid NTP issues. |
| Missing Claims | Verify issuer, aud, and exp claims are present. Use withRequiredClaims() in builders. |
| Performance Bottlenecks | Disable ext-gmp? Install it for faster crypto operations. |
| Caching Issues | Clear cache manually if keys rotate unexpectedly ($cache->delete('jwks:*')). |
use WebToken\JWT;
$jwt = JWT::decode($token, null, ['skip_verification' => true]);
\Log::debug('JWT Header:', [$jwt->getHeader()]);
\Log::debug('JWT Payload:', [$jwt->getPayload()]);
curl https://your-provider.com/jwks | jq
build():
// ❌ Wrong (throws error)
$verifier = $builder->build()->withState($state);
// ✅ Correct
$verifier = $builder->withState($state)->build();
id_token_signed_response_alg if the provider uses non-standard algorithms (e.gHow can I help you explore Laravel packages today?