Search by

un-dev-suisse / services-identities-client

Un dev Suisse

Verifies the service tokens issued by Services Identities and obtains them for outgoing calls, in Laravel services.

Package info

gitlab.com/un_dev_suisse/packages/services-identities-client

Issues

pkg:composer/un-dev-suisse/services-identities-client

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

v0.1.0 2026-10-20 12:29 UTC

This package is auto-updated.

Last update: 2026-10-25 00:07:06 UTC


README

Laravel package for the services of the platform. It does two things with the tokens issued by Services Identities: it verifies the token of an incoming call, and it obtains (and caches) the token for an outgoing call.

Requires PHP 8.3 with ext-sodium, Laravel 13.

Install

The package is read straight from its public GitLab repository, with a vcs repository: no package registry and no credentials are needed. Declare it once in the composer.json of the service, then require a version (the git tags):

composer config repositories.services-identities-client vcs https://gitlab.com/un_dev_suisse/services-identities-client.git
composer require un-dev-suisse/services-identities-client:^0.1

composer update then fetches new tags from GitLab, from a developer machine as well as while an image is built. The repository holds no secret, only the verification logic.

The service provider is discovered. Publish the configuration only if you need to change it (php artisan vendor:publish --tag=services-identities-config).

Configure

VariableRole
SERVICES_IDENTITIES_URLBase URL of Services Identities on the internal network, for example http://services-identities_app
SERVICES_IDENTITIES_SERVICEIdentifier of this service. Incoming tokens must be issued for it
SERVICES_IDENTITIES_ISSUERExpected issuer: IDENTITIES_TOKEN_ISSUER of Services Identities
SERVICES_IDENTITIES_CLIENT_ID, SERVICES_IDENTITIES_CLIENT_SECRETCredentials of this service, to obtain tokens. Only needed to call other services
SERVICES_IDENTITIES_JWKS_URLWhere the keys are published. Default: <url>/api/jwks.json
SERVICES_IDENTITIES_JWKS_CACHE_TTLSeconds the key set stays cached (300)
SERVICES_IDENTITIES_JWKS_REFETCH_COOLDOWNMinimum seconds between two refreshes triggered by an unknown key (30)
SERVICES_IDENTITIES_TOKEN_RENEW_MARGINA token is renewed this many seconds before it expires (30)
SERVICES_IDENTITIES_LEEWAYTolerated clock difference in seconds (5)
SERVICES_IDENTITIES_HTTP_TIMEOUTSeconds before a request to Services Identities is abandoned (5)

A required setting left empty raises MissingConfiguration instead of falling back to a default.

Protect routes

Route::middleware('service.token')->get('/status', ...);                  // any service allowed to call this one
Route::middleware('service.token:mail:send')->post('/mails', ...);        // needs the mail:send scope
Route::middleware('service.token:mail:send,mail:read')->get('/mails', ...); // needs both

The middleware checks the signature (EdDSA only, with the key published under the token's kid), the issuer, that the audience is this service, and the validity period. It answers 401 invalid_token with a WWW-Authenticate header, 403 insufficient_scope when a scope is missing, and 503 temporarily_unavailable when the keys can be neither read from the cache nor fetched. The reason of a refusal goes to the logs, never to the caller. In the route, the verified token is the service_token request attribute:

$token = $request->attributes->get('service_token');
$token->caller;            // 'budgets'
$token->scopes;            // ['mail:send']
$token->hasScope('mail:send');

Call another service

use UnDevSuisse\ServicesIdentities\ServiceClient;

$response = app(ServiceClient::class)
    ->http('mail', ['mail:send'])               // the target service and the scopes to ask for
    ->post('http://mail_app/api/mails', $payload);

http() returns an HTTP client request already carrying Authorization: Bearer <token>. The scopes are optional: without them the token carries every scope granted to this service on the target. The token is cached, per target and scopes, until SERVICES_IDENTITIES_TOKEN_RENEW_MARGIN seconds before it expires, then renewed. A refusal raises TokenRequestFailed, whose status and error hold the HTTP status and the OAuth code (invalid_client, access_denied, invalid_scope).

Keys

The public keys come from the JWKS of Services Identities and are cached for SERVICES_IDENTITIES_JWKS_CACHE_TTL seconds. A token with a kid that is not in the cached set refreshes it, at most once per cooldown: a key rotation needs no action in the services.

Development

composer install
composer test       # PHPUnit with Orchestra Testbench
composer lint       # Pint
composer analyse    # PHPStan (Larastan)

Publish a version

Tag the commit and push the tag: services pick it up on their next composer update.

git tag v0.1.0 && git push origin v0.1.0