PHPackages                             hvatum/oauth2-openid-connect-client - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. hvatum/oauth2-openid-connect-client

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

hvatum/oauth2-openid-connect-client
===================================

Generic OpenID Connect Provider for The PHP League OAuth2 Client with PAR, DPoP, and private\_key\_jwt support

v0.5.0(1w ago)0204↓71.4%11MITPHPPHP ^8.2

Since Mar 4Pushed 1w agoCompare

[ Source](https://github.com/hvatum/oauth2-openid-connect-client)[ Packagist](https://packagist.org/packages/hvatum/oauth2-openid-connect-client)[ RSS](/packages/hvatum-oauth2-openid-connect-client/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (23)Versions (8)Used By (1)

OpenID Connect Client for The PHP League OAuth2 Client
======================================================

[](#openid-connect-client-for-the-php-league-oauth2-client)

A generic OpenID Connect provider for [The PHP League's OAuth2 Client](https://github.com/thephpleague/oauth2-client), with built-in support for modern OAuth 2.0 security features:

- **OpenID Connect Discovery** — Automatic endpoint configuration via `.well-known/openid-configuration`
- **PAR** (Pushed Authorization Requests) — [RFC 9126](https://datatracker.ietf.org/doc/html/rfc9126)
- **PKCE** (Proof Key for Code Exchange) — [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) with S256
- **DPoP** (Demonstrating Proof of Possession) — [RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449)
- **Private Key JWT** client authentication — [RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523)
- **ID Token validation** — Signature verification, claim validation, nonce checking
- **Rich Authorization Requests** — [RFC 9396](https://datatracker.ietf.org/doc/html/rfc9396) parameter transport with extension hooks
- **RFC 9207** — Authorization Server Issuer Identification (mix-up attack protection)

Disclaimer
----------

[](#disclaimer)

OAuth2 and its related standards are complex topics to understand and to get right. This library strives to be correct but mistakes can be made. There is NO WARRANTY, use at your own risk, and please leave a bug report or a pull request if you find something that seems off.

Requirements
------------

[](#requirements)

- PHP 8.2 or later
- `ext-json`
- `ext-openssl`

Installation
------------

[](#installation)

```
composer require hvatum/oauth2-openid-connect-client
```

Basic Usage
-----------

[](#basic-usage)

The simplest setup — just point to the issuer:

```
use Hvatum\OpenIDConnect\Client\Provider\OpenIDConnectProvider;

$provider = new OpenIDConnectProvider([

    'clientId'     => 'your-client-id',
    'clientSecret' => 'your-client-secret',
    'redirectUri'  => 'https://your-app.example/callback',
    'issuer'       => 'https://your-idp.example',
]);
```

All endpoints (authorization, token, userinfo, JWKS, PAR) are automatically discovered from `{issuer}/.well-known/openid-configuration`.

### Authorization Code Flow

[](#authorization-code-flow)

```
// Step 1: Redirect user to authorization endpoint
if (!isset($_GET['code'])) {
    $authUrl = $provider->getAuthorizationUrl([
        'scope' => ['openid', 'profile', 'email'],
    ]);

    // Store state and nonce in session for validation
    $_SESSION['oauth2_state'] = $provider->getState();
    $_SESSION['oauth2_nonce'] = $provider->getNonce();
    $_SESSION['oauth2_pkce']  = $provider->getPkceCode();

    header('Location: ' . $authUrl);
    exit;
}

// Step 2: Handle callback
if ($_GET['state'] !== $_SESSION['oauth2_state']) {
    throw new \RuntimeException('Invalid state');
}

// Restore state from session
$provider->setNonce($_SESSION['oauth2_nonce']);

// Exchange code for tokens (iss is used for RFC 9207 mix-up attack protection)
$token = $provider->getAccessToken('authorization_code', [
    'code'          => $_GET['code'],
    'code_verifier' => $_SESSION['oauth2_pkce'],
    'iss'           => $_GET['iss'] ?? null,
]);

// Get user info (ID token claims merged with userinfo endpoint)
$user = $provider->getResourceOwner($token);
echo $user->getName();
echo $user->getEmail();
```

Advanced Usage
--------------

[](#advanced-usage)

### Private Key JWT Authentication (RFC 7523)

[](#private-key-jwt-authentication-rfc-7523)

Use `private_key_jwt` instead of `client_secret` for client authentication:

```
$provider = new OpenIDConnectProvider([
    'clientId'       => 'your-client-id',
    'redirectUri'    => 'https://your-app.example/callback',
    'issuer'         => 'https://your-idp.example',
    'privateKeyPath' => '/path/to/private-key.pem',  // or .jwk
    'keyId'          => 'your-key-id',                // optional if in JWK file
]);
```

Supports EC (ES256/ES384/ES512) and RSA (RS256/RS384/RS512, PS256/PS384/PS512) keys in both PEM and JWK formats.

#### Loading keys from environment variables

[](#loading-keys-from-environment-variables)

For 12-factor / Kubernetes-style deployments where the key is injected via an environment variable rather than mounted on disk, use `privateKey` to pass the raw PEM or JWK JSON content directly:

```
$provider = new OpenIDConnectProvider([
    'clientId'   => 'your-client-id',
    'issuer'     => 'https://your-idp.example',
    'privateKey' => getenv('OIDC_CLIENT_PRIVATE_KEY'), // raw PEM or JWK JSON
    'keyId'      => 'your-key-id',
]);
```

`privateKey` (raw content) and `privateKeyPath` (filesystem path) are mutually exclusive — setting both throws an `InvalidArgumentException` at construction. An empty string is treated as unset, so an unset env var bound to `''` falls back to a configured `privateKeyPath` rather than silently disabling client assertion.

### DPoP Token Binding (RFC 9449)

[](#dpop-token-binding-rfc-9449)

Bind access tokens to a cryptographic key pair to prevent token theft:

```
$provider = new OpenIDConnectProvider([
    'clientId'            => 'your-client-id',
    'redirectUri'         => 'https://your-app.example/callback',
    'issuer'              => 'https://your-idp.example',
    'privateKeyPath'      => '/path/to/client-key.pem',
    'dpopPrivateKeyPath'  => '/path/to/dpop-private.pem',
    'dpopPublicKeyPath'   => '/path/to/dpop-public.pem',
]);

// DPoP proofs are automatically included in token requests.
// For API calls with DPoP-bound tokens:
$response = $provider->makeDPopRequest('GET', 'https://api.example/resource', $token->getToken());
```

DPoP keys follow the same contract as `privateKey` / `privateKeyPath`: `dpopPrivateKey` and `dpopPublicKey` accept raw PEM/JWK content for env-var deployments, while `dpopPrivateKeyPath` and `dpopPublicKeyPath` accept filesystem paths. Each `*Key` option is mutually exclusive with its `*KeyPath`counterpart. The public key may be omitted entirely — it is derived from the private key.

### ID Token Validation

[](#id-token-validation)

ID tokens are automatically validated when fetching resource owner details. You can also validate manually:

```
$claims = $provider->validateIdToken($idTokenJwt, $expectedNonce);
```

Validates: signature (ES256/384/512, RS256/384/512, PS256/384/512), issuer, audience, expiration, nonce, and more.

### UserInfo Responses

[](#userinfo-responses)

`getResourceOwner()` accepts both plain JSON userinfo responses and signed JWT responses (OIDC Core §5.3.2, content-type `application/jwt`). Signed responses are verified against the provider JWKS with the same algorithm allow-listing as ID tokens (`iss`, `aud` and `sub` required, time claims checked when present, an unaccepted `azp` rejected (see [Authorized party validation](#authorized-party-azp-validation)) — though per OIDC Core §5.3.2 multiple audiences are accepted without `azp`, unlike ID tokens), and the JWT envelope and ID-token transport claims are stripped before the claims are merged with the ID token. Accepted algorithms come from `userinfo_signing_alg_values_supported` in discovery, falling back to the id\_token algorithms — override `getUserinfoSigningAlgValuesSupported()`in a subclass for servers that sign userinfo with algorithms they do not advertise. Encrypted (JWE) userinfo responses are not supported.

### Caching

[](#caching)

Well-known configuration and JWKS keys are cached using PSR-16 (SimpleCache). TTL is managed by the cache implementation, so expiry works correctly across PHP-FPM requests.

By default, a built-in filesystem cache is used. You can customize the directory and TTLs:

```
$provider = new OpenIDConnectProvider([
    // ...
    'cacheDir' => '/path/to/cache',          // default: sys_get_temp_dir()/oauth2-oidc/
    'wellKnownCacheTtl' => 86400,            // default: 86400 (24 hours)
    'jwksCacheTtl' => 3600,                  // default: 3600 (1 hour)
]);
```

Or provide your own PSR-16 cache implementation (e.g. Redis, Memcached):

```
$provider = new OpenIDConnectProvider([
    // ...
], [
    'cache' => $yourPsr16Cache, // Must implement Psr\SimpleCache\CacheInterface
]);
```

### PSR-3 Logging

[](#psr-3-logging)

Pass a PSR-3 logger for debug output:

```
$provider = new OpenIDConnectProvider([
    // ...
], [
    'logger' => $yourPsrLogger,
]);
```

Key Generation
--------------

[](#key-generation)

### EC Key Pair (for DPoP or client assertion)

[](#ec-key-pair-for-dpop-or-client-assertion)

```
openssl ecparam -name prime256v1 -genkey -noout -out private.pem
openssl ec -in private.pem -pubout -out public.pem
```

### RSA Key Pair (for client assertion)

[](#rsa-key-pair-for-client-assertion)

```
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
```

Extending for Specific Providers
--------------------------------

[](#extending-for-specific-providers)

This package is designed to be extended for provider-specific requirements:

```
use Hvatum\OpenIDConnect\Client\Provider\OpenIDConnectProvider;

class MyProvider extends OpenIDConnectProvider
{
    public const CLIENT_ASSERTION_TTL = 10; // Override default TTL

    protected function getDefaultScopes(): array
    {
        return ['openid', 'profile', 'my-custom-scope'];
    }

    protected function createResourceOwner(array $response, AccessToken $token)
    {
        return new MyResourceOwner($response);
    }
}
```

### Authorized party (azp) validation

[](#authorized-party-azp-validation)

An unaccepted `azp` claim fails validation on both ID tokens and signed userinfo responses; by default only this client's own ID is accepted. For deployments where a foreign `azp` is legitimate — e.g. Google-style hybrid app flows, where `azp` names the native app while `aud` names this client — pass the allow-list as an option:

```
new MyProvider([
    // ...
    'allowedAuthorizedParties' => ['native-app-client-id'],
]);
```

or override `isAcceptedAuthorizedParty()` for policies a static list cannot express. An allow-listed foreign `azp` is only accepted on single-audience JWTs, and multi-audience ID tokens always require `azp` to name this client itself — the allow-list does not loosen either rule.

### Client assertion audience

[](#client-assertion-audience)

The client assertion `aud` claim defaults to the issuer identifier, as required by [draft-ietf-oauth-rfc7523bis](https://datatracker.ietf.org/doc/draft-ietf-oauth-rfc7523bis/) §4 (updating RFC 7523 §3), which mandates the issuer identifier as the sole audience value and forbids the token endpoint URL. The draft — not yet an RFC, but in the RFC Editor queue — tightens the original RFC 7523 §3 rule (which allowed the token endpoint) and OpenID Connect Core §9 (which recommended it).

Legacy servers that only accept the token endpoint URL are still supported by overriding `getClientAssertionAudience()`:

```
class MyProvider extends OpenIDConnectProvider
{
    protected function getClientAssertionAudience(): string
    {
        return $this->tokenUrl;
    }
}
```

### Authorization Details (RFC 9396) and Profile Hooks

[](#authorization-details-rfc-9396-and-profile-hooks)

By default, `authorization_details` follows RFC 9396 parameter transport:

- Authorization request / PAR request: sent as JSON string parameter
- Token request: sent as JSON string parameter
- No default embedding into `client_assertion` claims

If a provider profile requires embedding `authorization_details` in `client_assertion`, override these hooks:

```
class MyProvider extends OpenIDConnectProvider
{
    protected function getAuthorizationDetailsForClientAssertion(array $params, ?array $authorizationDetails): ?array
    {
        if (($params['grant_type'] ?? '') !== 'client_credentials') {
            return null;
        }

        return $authorizationDetails;
    }

    protected function shouldSendAuthorizationDetailsInTokenRequestBody(array $params, ?array $authorizationDetails): bool
    {
        return false;
    }
}
```

Supported RFCs
--------------

[](#supported-rfcs)

RFCFeatureStatus[RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749)OAuth 2.0 Authorization FrameworkSupported (via League)[RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)JSON Web Key (JWK)Supported[RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523)JWT Bearer Client AuthenticationSupported[RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636)PKCE (S256)Supported[RFC 7638](https://datatracker.ietf.org/doc/html/rfc7638)JWK ThumbprintSupported[RFC 9126](https://datatracker.ietf.org/doc/html/rfc9126)Pushed Authorization Requests (PAR)Supported[RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207)Authorization Server Issuer IdentificationSupported[RFC 9396](https://datatracker.ietf.org/doc/html/rfc9396)Rich Authorization RequestsSupported[RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449)DPoP (Demonstrating Proof of Possession)SupportedLicense
-------

[](#license)

MIT License. See [LICENSE](LICENSE) for details.

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance98

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~26 days

Recently: every ~39 days

Total

7

Last Release

11d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1156173?v=4)[hvatum](/maintainers/hvatum)[@hvatum](https://github.com/hvatum)

---

Top Contributors

[![hvatum](https://avatars.githubusercontent.com/u/1156173?v=4)](https://github.com/hvatum "hvatum (106 commits)")

---

Tags

jwtAuthenticationidentityoauthoauth2authorizationOpenID Connectoidcdpoppar

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/hvatum-oauth2-openid-connect-client/health.svg)

```
[![Health](https://phpackages.com/badges/hvatum-oauth2-openid-connect-client/health.svg)](https://phpackages.com/packages/hvatum-oauth2-openid-connect-client)
```

###  Alternatives

[laravel/framework

The Laravel Framework.

34.9k556.2M21.5k](/packages/laravel-framework)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

762297.9k53](/packages/civicrm-civicrm-core)[google/auth

Google Auth Library for PHP

1.4k302.1M240](/packages/google-auth)[simplesamlphp/simplesamlphp-module-oidc

A SimpleSAMLphp module adding support for the OpenID Connect protocol

5018.6k1](/packages/simplesamlphp-simplesamlphp-module-oidc)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.8M675](/packages/shopware-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
