facile-it/php-openid-client
Full-featured PHP OpenID Connect/OAuth2 client with discovery and dynamic client registration. Supports authorization flows, refresh/client credentials grants, userinfo & ID tokens, JWT signing/encryption, request objects, token revocation/introspection, and advanced client auth.
## Getting Started
### Minimal Setup
1. **Install the package**:
```bash
composer require facile-it/php-openid-client
Ensure your project meets PSR-7 requirements (e.g., guzzlehttp/psr7 or nyholm/psr7).
Configure a basic client:
use Facile\OpenIDClient\Client\ClientBuilder;
use Facile\OpenIDClient\Issuer\IssuerBuilder;
use Facile\OpenIDClient\Client\Metadata\ClientMetadata;
$issuer = (new IssuerBuilder())->build('https://provider.com/.well-known/openid-configuration');
$clientMetadata = ClientMetadata::fromArray([
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'redirect_uris' => ['https://your-app.com/callback'],
]);
$client = (new ClientBuilder())
->setIssuer($issuer)
->setClientMetadata($clientMetadata)
->build();
First use case: Redirect to OpenID provider:
$authorizationService = (new AuthorizationServiceBuilder())->build();
$authUrl = $authorizationService->getAuthorizationUri($client, ['scope' => 'openid profile']);
return redirect($authUrl);
Handle callback:
$callbackParams = $authorizationService->getCallbackParams($request, $client);
$tokenSet = $authorizationService->callback($client, $callbackParams);
Use the middleware stack to handle OAuth flows seamlessly:
// app/Http/Middleware/AuthenticateWithOpenID.php
use Facile\OpenIDClient\Middleware\SessionCookieMiddleware;
use Facile\OpenIDClient\Middleware\AuthRedirectHandler;
use Facile\OpenIDClient\Middleware\CallbackMiddleware;
use Facile\OpenIDClient\Middleware\UserInfoMiddleware;
public function handle($request, Closure $next) {
$middleware = app(SessionCookieMiddleware::class);
$middleware->process($request, function ($request) use ($next) {
$middleware = app(AuthRedirectHandler::class);
return $middleware->process($request, function ($request) use ($next) {
return $next($request);
});
});
return $next($request);
}
Create a dedicated service class to encapsulate OpenID logic:
// app/Services/OpenIDService.php
class OpenIDService {
public function __construct(
private AuthorizationService $authService,
private UserInfoService $userInfoService,
private Client $client
) {}
public function authenticate(Request $request) {
$callbackParams = $this->authService->getCallbackParams($request, $this->client);
$tokenSet = $this->authService->callback($this->client, $callbackParams);
return $this->userInfoService->getUserInfo($this->client, $tokenSet);
}
}
Register clients dynamically at runtime:
$registrationService = (new RegistrationServiceBuilder())->build();
$metadata = $registrationService->register($issuer, [
'client_name' => 'Laravel App',
'redirect_uris' => ['https://app.com/callback'],
'token_endpoint_auth_method' => 'client_secret_basic',
]);
Handle token refresh and revocation:
// Refresh token
$tokenSet = $authService->refresh($client, $oldTokenSet->getRefreshToken());
// Revoke token
$revocationService = (new RevocationServiceBuilder())->build();
$revocationService->revoke($client, $tokenSet->getAccessToken());
Use signed request objects for enhanced security:
$requestObject = (new RequestObjectFactory())->create($client, [
'nonce' => bin2hex(random_bytes(16)),
'acr_values' => ['urn:mace:incommon:iap:silver'],
]);
$authRequest = AuthRequest::fromParams([
'client_id' => $client->getMetadata()->getClientId(),
'redirect_uri' => $client->getMetadata()->getRedirectUris()[0],
'request' => $requestObject,
]);
State/Nonce Mismatch:
Always use SessionCookieMiddleware to persist state and nonce parameters. Failure to validate these can lead to CSRF attacks.
// Ensure this is the first middleware in your stack
$middleware = new SessionCookieMiddleware(app('cache.store'));
Redirect URI Mismatch:
The redirect_uris in your client metadata must exactly match the callback URL used in the authorization request. Typos here will break the flow.
Token Introspection vs. UserInfo:
Introspection (/introspect) validates tokens, while UserInfo (/userinfo) fetches claims. Don’t confuse the two:
// Introspect (validate)
$introspectionService->introspect($client, $token);
// UserInfo (fetch claims)
$userInfoService->getUserInfo($client, $tokenSet);
JWKS Caching: The library caches JWKS (JSON Web Key Sets) by default for 1 day. Adjust the TTL in production if your provider updates keys frequently:
$jwksProviderBuilder->setCacheTtl(3600); // 1 hour
Enable Verbose Logging:
Use Monolog to log HTTP requests/responses for debugging:
$clientBuilder->setHttpClient(new \GuzzleHttp\Client([
'handler' => \GuzzleHttp\HandlerStack::create(new \GuzzleHttp\Middleware::log(
app('logger')->getHandler(),
new \GuzzleHttp\MessageFormatter()
)),
]));
Validate Token Claims: Manually verify ID tokens before trusting them:
$idToken = $tokenSet->getIdToken();
$claims = $idToken->getClaims();
if ($claims['iss'] !== $issuer->getIssuer() || $claims['aud'] !== $client->getMetadata()->getClientId()) {
throw new \RuntimeException('Invalid token issuer or audience');
}
Handle Missing Scopes:
Always check if the openid scope is included in the authorization request. Without it, the provider won’t return an ID token:
$authUrl = $authService->getAuthorizationUri($client, ['scope' => 'openid profile email']);
Cache Issuer Metadata: Cache the OpenID configuration metadata to avoid repeated HTTP calls:
$metadataProviderBuilder->setCache(app('cache'))->setCacheTtl(86400); // 24 hours
Reuse HTTP Clients: Instantiate HTTP clients (e.g., Guzzle) once and reuse them across requests to avoid connection overhead:
$httpClient = new \GuzzleHttp\Client(['timeout' => 10]);
$clientBuilder->setHttpClient($httpClient);
Custom Claim Handling:
Extend the ClaimsParser interface to handle provider-specific claims:
class CustomClaimsParser implements ClaimsParserInterface {
public function parse(array $claims, Client $client): array {
// Add custom logic (e.g., transform provider-specific fields)
return $claims;
}
}
Middleware Chaining: Combine middlewares for complex flows (e.g., pre-auth validation + post-auth actions):
$middlewareStack = [
new SessionCookieMiddleware($cache),
new ClientProviderMiddleware($client),
new AuthRequestProviderMiddleware($authRequest),
new AuthRedirectHandler($authService),
new CallbackMiddleware($authService),
new UserInfoMiddleware($userInfoService),
];
Dynamic Client Metadata:
Use the ClientMetadata builder to dynamically set metadata (e.g., for multi-tenant apps):
$metadata = ClientMetadata::fromArray([
'client_id' => $tenant->client_id,
'client_secret' => $tenant->client_secret,
'redirect_uris' => [$tenant->callback_url],
]);
Session Driver:
Ensure your Laravel session driver is PSR-16 compatible (e.g., array, database, or redis) for SessionCookieMiddleware:
// config/cache.php
'default' => env('CACHE_DRIVER', 'array'),
Service Provider Binding: Bind the library’s services in a service provider:
// app/Providers/OpenIDServiceProvider.php
public function register() {
$this->app->
How can I help you explore Laravel packages today?