PHPackages                             ingenerator/oidc-token-verifier - 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. [Security](/categories/security)
4. /
5. ingenerator/oidc-token-verifier

ActiveLibrary[Security](/categories/security)

ingenerator/oidc-token-verifier
===============================

Lightweight library to verify OIDC tokens against a public discovery document / JWKS collection

v1.5.0(1w ago)021.8k↓39%BSD-3-ClausePHPPHP ~8.2.0 || ~8.3.0 || ~8.4.0CI failing

Since Dec 8Pushed 11mo ago2 watchersCompare

[ Source](https://github.com/ingenerator/oidc-token-verifier)[ Packagist](https://packagist.org/packages/ingenerator/oidc-token-verifier)[ Docs](https://github.com/ingenerator/oidc-token-verifier)[ RSS](/packages/ingenerator-oidc-token-verifier/feed)WikiDiscussions 1.x Synced yesterday

READMEChangelog (9)Dependencies (14)Versions (14)Used By (0)

oidc-token-verifier is a lightweight PHP validator for [OIDC ID Tokens](https://openid.net/specs/openid-connect-core-1_0.html#IDToken)as used in the [OpenID Connect](https://openid.net/connect/) protocol.

[![Tests](https://github.com/ingenerator/oidc-token-verifier/workflows/Run%20tests/badge.svg)](https://github.com/ingenerator/cloud-tasks-wrapper/actions)

`$> composer require ingenerator/oidc-token-verifier`

Usage of OIDC tokens
====================

[](#usage-of-oidc-tokens)

In the full OpenID Connect specification, the ID Token forms part of a multi-step end-user authorization flow. This is a similar concept to using OAuth to authenticate users based on a third-party auth provider.

However, OIDC Tokens can also be used for lightweight server-to-server authentication. For example, they can be used to authorise HTTP requests from [Google Cloud Tasks](https://cloud.google.com/tasks/docs/creating-http-target-tasks#token).

Server-to-Server flows like this do not require the full OpenID Connect protocol. They only require the ability to verify the ID Token itself. The ID Token is a [JWT](https://jwt.io/), which is cryptographically signed by the issuer. Authenticating the token involves verifying the signature against the issuer's public keys, which are available from a well-known HTTP endpoint, and then performing some checks on the content of the token itself.

Although there are number of PHP JWT libraries, we have struggled to find any that support the certificate discovery / claim assertion phases of the process. This library fills that gap.

Note that all cryptographic / JWT-level operations are delegated to the firebase/php-jwt package. Also note that at present we only support RSA keys and the RS256 token algorithm.

Usage
=====

[](#usage)

You validate a token with the `OIDCTokenVerifier`. There is also a `MockTokenVerifier` using the same interface for unit testing purposes.

In the simplest case you would do something like this:

```
// Where the AUTHORIZATION header is `Bearer {token}`
use Google\Auth\Cache\SysVCacheItemPool;use Ingenerator\OIDCTokenVerifier\OIDCTokenVerifier;use Ingenerator\OIDCTokenVerifier\OpenIDDiscoveryCertificateProvider;use Ingenerator\OIDCTokenVerifier\TokenConstraints;use Ingenerator\OIDCTokenVerifier\TokenVerificationResult;use Psr\Log\NullLogger;use test\mock\Ingenerator\OIDCTokenVerifier\Cache\MockCacheItemPool;
[$bearer, $jwt] = explode(' ', $_SERVER['HTTP_AUTHORIZATION']);
$verifier = new OIDCTokenVerifier(
    new OpenIDDiscoveryCertificateProvider(
        new \GuzzleHttp\Client,
        // Any psr-6 CacheItemPoolInterface implementation, used for caching issuer certificates
        new CacheItemPoolInterface,
        new NullLogger // Any PSR logger
    ),
    // You *must* explicitly provide the issuer your application expects to receive tokens from.
    // The verifier will *only* request certificates from this issuer. Otherwise, any third party could set up an HTTP
    // certificate endpoint and send you tokens signed by them.
    //
    // If your application may receive tokens from more than one issuer, you will need to (securely) identify the issuer
    // of a specific token and then create an appropriate verifier.
    //
    'https://accounts.google.com'
);

// See below for details of the TokenConstraints argument
$result = $verifier->verify($jwt, TokenConstraints::signatureCheckOnly());

// You can either interrogate the result like this
if ( ! $result->isVerified()) {
    echo "NOT AUTHORISED\n";
    echo $result->getFailure()->getMessage()."\n";
} else {
    // The JWT payload is available from the result object
    echo "Authorised as ".$result->getPayload()->email."\n";
}

// Or if you'd prefer to throw an exception on failed auth this will:
// - Throw TokenVerificationFailedException if verification failed
// - Return the verified result if successful
$result = TokenVerificationResult::enforce($result);
```

### Extra constraints

[](#extra-constraints)

By default, the library only performs basic JWT validation - signature, expiry time / not before time etc.

For security, **additional verification is almost always required**. For example, any Google Cloud Platform user can produce a valid JWT signed by `https://accounts.google.com` so you would usually want to authorize based on both the `audience` (that the token was created for) and the `email` (the service account used to create it).

The library provides support for these common constraints out of the box:

```
$verifier->verify($jwt, new TokenConstraints([
    // The audience (`aud` claim) of the JWT must exactly match this value
    // Some google services use the URL that is being called. Others provide a custom value - an app/client ID, etc
    'audience_exact' => 'https://my.app.com/task-handler-url',

    // The audience (`aud` claim) of the JWT is a URL and the path (and querystring if any) must match this value
    // In some loadbalanced environments it's hard to detect the external protocol or hostname from an incoming
    // request - e.g. a request to https://my.app.loadbalancer may appear to PHP as being to http://app.cluster.local.
    // Although this can be worked round with custom headers (X_FORWARDED_PROTO etc) these introduce other risks and
    // ultimately couple the app implementation to architectural concerns. In many cases, it's enough to verify the
    // the resource the token was generated for (path and querystring) without caring about scheme and hostname. This
    // alone prevents using a stolen token to perform a different operation. Cross-environment / cross-site attacks
    // are instead protected by using different service accounts for each separate logical system so that e.g a token
    // generated for QA cannot ever authorise that operation in production regardless of the hostnames used.
    'audience_path_and_query' => 'http://appserver.internal/action?record_id=15',

    // The JWT must contain an `email` claim, and it must exactly match this value
    'email_exact' => 'my-service-account@myproject.serviceaccount.test',

    // The JWT must contain an `email` claim, and it must exactly match one of these values
    // Useful when you have a short list of service accounts that may be allowed to call your endpoint
    'email_exact' => [
        'my-service-account@myproject.serviceaccount.test',
        'my-service-account@myotherproject.serviceaccount.test',
    ],

    // The JWT must contain an `email` claim, and it must match this regex
    // Useful when you want to e.g. authorize all service accounts in a particular domain - use with caution!
    'email_match' => '/@myproject.serviceaccount.test$/'
]));
```

You can easily support additional custom constraints e.g. to verify additional custom claims:

```
class MyTokenConstraints extends TokenConstraints {

    protected static function getAllMatchers(): array {
        $matchers = parent::getAllMatchers();
        // Constraint matchers are an array of {name} => boolean function indicating if the payload matches
        $matchers['user_role_contains'] = function (\stdClass $payload, string $expect) {
            // $payload is the decoded JWT
            // We check it has a custom claim ->user_roles as an array of roles
            return in_array($expect, $payload->user_roles ?? [], TRUE);
        };
        return $matchers;
    }
}

$verifier->verify($jwt, new MyTokenConstraints([
    'audience_exact'     => 'https://foo.bar/something',
    'user_role_contains' => 'administrator'
]));
```

If your app handles authorization separately (or for testing purposes) you can use the `TokenConstraints::signatureCheckOnly()` method to create an empty set of constraints.

### Certificate discovery and caching

[](#certificate-discovery-and-caching)

By default, the library uses the OpenIDDicoveryCertificateProvider to dynamically fetch public certificates for a given issuer. This uses the `{issuer}/.well-known/openid-configuration` discovery document to find the issuer's JWKS url. Certificates are then fetched from the JWKS url, decoded and cached (in a PSR-6 cache) for subsequent requests.

For obvious reasons, both the discovery document and the JWKS **must** be served over HTTPS. In development environments e.g. if working against an emulator, you may not have HTTPS available. In this case, pass the `allow_insecure => TRUE` option to enable fetching certs over HTTP.

The cache lifetime is based on the `Expires` header of the JWKS response. Note that we do not cache (or pay attention to) the cache headers on the OpenID Discovery Document itself. If an issuer changes their `jwks_uri` this will not be detected until the JWKS response itself expires.

If the provider does not include an `Expires` header on their response, the result will be cached for 10 minutes by default. You can customise this with the `cache_expires_if_no_header` option passed to the OpenIDDiscoveryCertificateProvider constructor.

Occasionally, network / issuer errors might occur when fetching or refreshing certificates. Since JWKS change fairly infrequently, the default behaviour is to log failures but use a stale cache value for up to 2 hours. This can be configured with the `cache_refresh_grace_period` option to OpenIDDiscoveryCertificateProvider.

HTTP-based discovery is the simplest and recommended solution, as it allows for issuer-controlled certificate and key rotation. However, an `ArrayCertificateProvider` is available (or you can provide your own implementation) if you would prefer to work with a hardcoded / alternative source of issuer certificates.

Contributing
============

[](#contributing)

Contributions are welcome but please contact us (e.g. by filing an issue) before you start work on anything substantial : we may have particular requirements / opinions that differ from yours.

Contributors
============

[](#contributors)

This package has been sponsored by [inGenerator Ltd](http://www.ingenerator.com)

- Andrew Coulton [acoulton](https://github.com/acoulton) - Lead developer

Licence
=======

[](#licence)

Licensed under the [BSD-3-Clause Licence](LICENSE)

###  Health Score

51

↑

FairBetter than 95% of packages

Maintenance72

Regular maintenance activity

Popularity26

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity79

Established project with proven stability

 Bus Factor1

Top contributor holds 54.8% 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 ~155 days

Total

14

Last Release

8d ago

Major Versions

0.3.x-dev → v1.0.02023-10-26

PHP version history (6 changes)v0.1.0PHP ^7.4

v0.2.0PHP ^7.4 || ~8.0.0

v0.3.0PHP ~8.0.0 || ~8.1.0 || ~8.2.0

v1.2.0PHP ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0

v1.3.0PHP ~8.2.0 || ~8.3.0

v1.4.0PHP ~8.2.0 || ~8.3.0 || ~8.4.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/f6ebcc608a86cec9a3ce7a4932994e2b89fabbfcdde69aff43e8139f34e07a5e?d=identicon)[acoulton](/maintainers/acoulton)

---

Top Contributors

[![acoulton](https://avatars.githubusercontent.com/u/416566?v=4)](https://github.com/acoulton "acoulton (23 commits)")[![craig410](https://avatars.githubusercontent.com/u/1156379?v=4)](https://github.com/craig410 "craig410 (19 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/ingenerator-oidc-token-verifier/health.svg)

```
[![Health](https://phpackages.com/badges/ingenerator-oidc-token-verifier/health.svg)](https://phpackages.com/packages/ingenerator-oidc-token-verifier)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k15](/packages/tempest-framework)[google/auth

Google Auth Library for PHP

1.4k294.2M219](/packages/google-auth)[symfony/symfony

The Symfony PHP framework

31.4k87.2M2.2k](/packages/symfony-symfony)[symfony/cache

Provides extended PSR-6, PSR-16 (and tags) implementations

4.2k373.5M3.3k](/packages/symfony-cache)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M737](/packages/sylius-sylius)[civicrm/civicrm-core

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

751291.4k43](/packages/civicrm-civicrm-core)

PHPackages © 2026

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