PHPackages                             stromcom/auth-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. stromcom/auth-client

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

stromcom/auth-client
====================

Official PHP client for the STROMCOM SSO / OAuth 2.0 / OIDC server (auth.stromcom.cz).

v2.0.0(2w ago)0347↓39.5%MITPHPPHP ^8.3CI passing

Since May 13Pushed 2w agoCompare

[ Source](https://github.com/stromcom/php-auth-client)[ Packagist](https://packagist.org/packages/stromcom/auth-client)[ Docs](https://auth.stromcom.cz)[ RSS](/packages/stromcom-auth-client/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (4)Dependencies (4)Versions (5)Used By (0)

stromcom/auth-client
====================

[](#stromcomauth-client)

Official PHP client for the STROMCOM SSO server (**auth.stromcom.cz**). Implements OAuth 2.0 Authorization Code + PKCE, Client Credentials, JWT verification via JWKS with caching, UserInfo and logout. No framework dependencies, zero external JWT libraries.

> **Status:** stable. Strict RFC 9068 for access tokens (`typ=at+jwt`, required claims `iss`, `exp`, `aud`, `sub`, `client_id`, `iat`, `jti`) and OIDC Core 1.0 §3.1.3.7 for id\_tokens (audience, `azp`, nonce binding).
>
> Default issuer points to `https://auth.stromcom.cz`. For local development against a dev auth server, override `issuer` accordingly.

---

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

[](#installation)

```
composer require stromcom/auth-client
```

**Requirements:** PHP 8.3+, `ext-curl`, `ext-json`, `ext-openssl`.

**Runtime dependencies:** `lcobucci/jwt` (and its transitive `psr/clock`). That's it — no Guzzle, no PSR-7, no framework integration. JWT parsing, signature verification and temporal-claim checks go through `lcobucci/jwt`; JWKS fetching, caching, key-rotation orchestration and the OAuth grant flows are in-house.

---

Quickstart
----------

[](#quickstart)

```
use Stromcom\AuthClient\Client;
use Stromcom\AuthClient\Configuration;

$auth = new Client(new Configuration(
    clientId:     getenv('AUTH_CLIENT_ID'),
    clientSecret: getenv('AUTH_CLIENT_SECRET'),
    redirectUri:  'https://my-app.stromcom.cz/oauth/callback',
));
```

### Web application — user login

[](#web-application--user-login)

```
// 1. Anywhere a protected page needs auth — start the flow.
session_start();
[$url, $pkce, $state, $nonce] = $auth->beginAuthorization();
$_SESSION['oauth_verifier'] = $pkce->verifier;
$_SESSION['oauth_state']    = $state;
$_SESSION['oauth_nonce']    = $nonce; // null if `openid` is not in scope
header('Location: ' . $url);

// 2. In your /oauth/callback handler — validate state, exchange code.
if (!hash_equals($_SESSION['oauth_state'], $_GET['state'])) {
    exit('CSRF');
}
$tokens = $auth->exchangeCode($_GET['code'], $_SESSION['oauth_verifier']);

// 2b. Verify the OIDC id_token (binds the response to this session via nonce).
$auth->verifyIdToken($tokens->idToken, $_SESSION['oauth_nonce']);
unset($_SESSION['oauth_nonce']);

// 3. Per request — verify the bearer access token (JWKS is cached for 1 h).
$claims = $auth->verify($tokens->accessToken, $auth->configuration->clientId);
if ($claims->hasGroup('translate-editor')) {
    // authorize
}
```

Full walkthrough: [docs/auth-code-flow.md](docs/auth-code-flow.md).

### Service account — machine-to-machine

[](#service-account--machine-to-machine)

```
$auth = new Client(new Configuration(
    clientId:     'svc_ci_xxxxx',
    clientSecret: getenv('AUTH_CLIENT_SECRET'),
));

$tokens = $auth->clientCredentials();

$response = $http->get('https://api.stromcom.cz/v1/things', [
    'headers' => ['Authorization' => $tokens->authorizationHeader()],
]);
```

For long-running processes, cache the token until it nears expiry — see [examples/service-account-cached.php](examples/service-account-cached.php). Full walkthrough: [docs/service-account.md](docs/service-account.md).

### Refresh

[](#refresh)

```
$tokens = $auth->refresh($oldRefreshToken);
// The server rotates: the OLD refresh token is invalidated immediately.
// Persist $tokens->refreshToken right away.
```

### Logout

[](#logout)

```
header('Location: ' . $auth->logoutUrl('https://my-app.stromcom.cz/'));
```

Logout clears the SSO session cookie on auth.stromcom.cz. Tokens you already issued remain valid until their `exp` — clear your own cookies too.

---

Claims — object API
-------------------

[](#claims--object-api)

`$auth->verify($jwt, $expectedAudience)` returns a `Claims` value object. Don't dig into the raw payload — use the rich API:

```
$claims = $auth->verify($jwt, $auth->configuration->clientId);

// Identity
$claims->subject;              // sub
$claims->email;                // ?string
$claims->emailVerified;        // ?bool
$claims->name;                 // ?string (display name, scope `profile`)
$claims->givenName;            // ?string (scope `profile`)
$claims->familyName;           // ?string (scope `profile`)
$claims->phoneNumber;          // ?string E.164 (scope `phone`)
$claims->phoneNumberVerified;  // ?bool   (scope `phone`)
$claims->isAdmin;              // bool
$claims->displayName();        // name → email → client_name → sub
$claims->audience();           // first aud
$claims->isExpired();
$claims->secondsUntilExpiration();

// User vs service tokens
$claims->isUser();             // token_use=user
$claims->isService();          // token_use=service
$claims->clientId;             // service token only
$claims->clientName;           // service token only

// Roles (project-scoped: "{prefix}.{role}")
$claims->roles;                                  // list
$claims->hasRole('translator.editor');
$claims->hasAnyRole('translator.editor', 'translator.admin');
$claims->hasAllRoles('deploy.admin', 'deploy.viewer');
$claims->hasProjectRole('translator', 'editor'); // == hasRole('translator.editor')
$claims->rolesForProject('translator');          // ['editor', 'admin']  (prefix stripped)

// Groups (free-form labels)
$claims->groups;
$claims->hasGroup('vip-users');
$claims->hasAnyGroup('beta', 'early-access');
$claims->hasAllGroups('beta', 'vip-users');

// Scopes
$claims->scopes;
$claims->hasScope('email');

// Guard helpers — throw AuthorizationException if missing
$claims->requireRole('translator.editor');
$claims->requireAnyRole('translator.editor', 'translator.admin');
$claims->requireGroup('vip-users');
$claims->requireScope('email');
$claims->requireUserToken();    // throws if a service token was presented
$claims->requireServiceToken();

// Escape hatch for non-standard claims
$claims->claim('custom_thing');
$claims->all; // raw payload
```

Full reference: [docs/jwt-verification.md](docs/jwt-verification.md).

---

Configuration
-------------

[](#configuration)

ParameterDefaultDescription`clientId`(required)`cli_…` / `svc_…` issued in the admin UI`clientSecret``null`Required for confidential clients &amp; `client_credentials``redirectUri``null`Required for `authorization_code``issuer``https://auth.stromcom.cz`Server base URL — for local dev use `http://localhost:8003``defaultScopes``['openid','profile','email','groups']`Used when `beginAuthorization()` is called without `$scopes``timeout``10`HTTP timeout in seconds`jwksTtl``3600`JWKS cache TTL in seconds`leeway``30`JWT clock-skew tolerance in seconds`userAgent``stromcom-auth-client-php/1.0`Sent on every outbound requestEndpoints are derived from `issuer`. To override (rare — e.g. a reverse proxy), pass `authorizationEndpoint`, `tokenEndpoint`, `userInfoEndpoint`, `logoutEndpoint`, `jwksUri` explicitly.

---

JWKS caching
------------

[](#jwks-caching)

The server publishes `Cache-Control: max-age=3600` on `/.well-known/jwks.json`. The verifier caches the document so per-request verification does not call the auth server. Pick the backend that matches your runtime:

BackendUse it for`InMemoryJwksCache`Per-process. CLI scripts. Long-running workers (RoadRunner).`ApcuJwksCache`**AWS Lambda (Bref) + any PHP-FPM** — shared memory, fastest`FileJwksCache`Single-host without APCu (rare)```
use Stromcom\AuthClient\Jwks\ApcuJwksCache;     // Lambda / FPM
use Stromcom\AuthClient\Jwks\InMemoryJwksCache; // CLI / workers
use Stromcom\AuthClient\Jwks\FileJwksCache;     // fallback

$auth = new Client($configuration, jwksCache: new ApcuJwksCache());
```

Implement `JwksCacheInterface` for Redis/Memcached/PSR-16 backends. On `kid`miss the cache is invalidated and re-fetched once automatically — that's how key rotation works without restart.

---

Exceptions
----------

[](#exceptions)

ClassWhen`ConfigurationException`Missing required field in `Configuration``TransportException`Network failure (cURL error, DNS, TLS, timeout)`OAuthServerException`Auth server returned an `error` (e.g. `invalid_grant`, `invalid_client`).`TokenVerificationException`JWT signature / `iss` / `aud` / `exp` / `typ` / required-claim / `nonce` validation failed`AuthorizationException`Missing role / group / scope, wrong `token_use``AuthClientException`Base — catch this for anything thrown by the SDKFull mapping with retry guidance: [docs/error-handling.md](docs/error-handling.md).

---

Examples
--------

[](#examples)

FileDemonstrates[examples/web-app-callback.php](examples/web-app-callback.php)Full auth-code+PKCE flow (login / callback / api / logout)[examples/service-token.php](examples/service-token.php)M2M client\_credentials, one-shot[examples/service-account-cached.php](examples/service-account-cached.php)M2M with token caching for long-running workers[examples/verify-token.php](examples/verify-token.php)Resource-server style: verify Bearer JWT on inbound requests[examples/psr15-middleware.php](examples/psr15-middleware.php)Reusable PSR-15 middleware for any PSR-15 framework[examples/lambda-handler.php](examples/lambda-handler.php)AWS Lambda (Bref) handler with APCu-backed JWKS cache[examples/scope-authorization.php](examples/scope-authorization.php)Scope/role-based access control patterns[examples/smoke.php](examples/smoke.php)End-to-end smoke against a running auth server---

Local development against a dev auth server
-------------------------------------------

[](#local-development-against-a-dev-auth-server)

```
# 1. Register a client in /admin/clients with redirect URI http://localhost:9000/callback
#    (or create a service-account client for M2M flows).

# 2. Run an example against your dev auth server
AUTH_ISSUER=http://localhost:8003 \
AUTH_CLIENT_ID=cli_xxx \
AUTH_CLIENT_SECRET=... \
php -S localhost:9000 examples/web-app-callback.php
```

---

Testing
-------

[](#testing)

```
composer install
composer test     # PHPUnit (17 tests, 60 assertions)
composer phpstan  # static analysis (level 8)
composer ca       # phpstan + tests
```

Unit tests use no network and no live auth server. To smoke-test the wire protocol against a running server, run `examples/smoke.php` with valid credentials in env.

---

Further reading
---------------

[](#further-reading)

- [docs/architecture.md](docs/architecture.md) — package internals, design decisions
- [docs/auth-code-flow.md](docs/auth-code-flow.md) — web app deep dive (PKCE, state, callback)
- [docs/service-account.md](docs/service-account.md) — M2M deep dive (caching, retry, secret rotation)
- [docs/jwt-verification.md](docs/jwt-verification.md) — JWKS, claim semantics, key rotation
- [docs/error-handling.md](docs/error-handling.md) — exception hierarchy, retry strategy
- [docs/security.md](docs/security.md) — PKCE, state, secret storage, token storage
- [CHANGELOG.md](CHANGELOG.md)

For contributors and AI assistants working on this package: [CLAUDE.md](CLAUDE.md).

---

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance98

Actively maintained with recent releases

Popularity17

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

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 ~4 days

Total

4

Last Release

14d ago

Major Versions

v1.2.0 → v2.0.02026-05-26

### Community

Maintainers

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

---

Top Contributors

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

---

Tags

jwtoauth2oauth2-clientphpssostromcomjwtclientauthSSOoauth2oidcstromcom

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/stromcom-auth-client/health.svg)

```
[![Health](https://phpackages.com/badges/stromcom-auth-client/health.svg)](https://phpackages.com/packages/stromcom-auth-client)
```

###  Alternatives

[tymon/jwt-auth

JSON Web Token Authentication for Laravel and Lumen

11.5k50.9M364](/packages/tymon-jwt-auth)[thenetworg/oauth2-azure

Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client

24610.3M71](/packages/thenetworg-oauth2-azure)[patrickbussmann/oauth2-apple

Sign in with Apple OAuth 2.0 Client Provider for The PHP League OAuth2-Client

1142.7M10](/packages/patrickbussmann-oauth2-apple)[jeremy379/laravel-openid-connect

OpenID Connect support to the PHP League's OAuth2 Server. Compatible with Laravel Passport.

58403.6k8](/packages/jeremy379-laravel-openid-connect)[maicol07/flarum-ext-sso

SSO for Flarum

468.7k](/packages/maicol07-flarum-ext-sso)[simplesamlphp/simplesamlphp-module-oidc

A SimpleSAMLphp module adding support for the OpenID Connect protocol

5017.7k1](/packages/simplesamlphp-simplesamlphp-module-oidc)

PHPackages © 2026

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