Search by

jield-webdev / jield-api-tools

japaveh

Own implementation of API endpoints. This repository is based on the Laminas API tools but includes shared functionality to easily create API endpoints

Package info

github.com/jield-webdev/jield-api-tools

pkg:composer/jield-webdev/jield-api-tools

Statistics

Installs: 11 497

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 2

v2.0.0-beta.2 2026-10-29 10:50 UTC

This package is auto-updated.

Last update: 2026-11-06 04:04:49 UTC


README

Own implementation of API endpoints. This repository is based on the Laminas API tools but includes shared functionality to easily create API endpoints

Development of version 2 takes place on 2.x. The first prerelease is v2.0.0-beta.1; the existing 1.x line remains separate. Version 2 requires PHP 8.4 or 8.5.

Install the first beta explicitly:

composer require 'jield-webdev/jield-api-tools:2.0.0-beta.1' 'solarium/solarium:dev-master'

The package currently depends on Solarium's development branch. The explicit Solarium requirement allows that dependency without changing the application's project-wide minimum-stability.

Some upstream dependencies still restrict PHP to 8.4. On PHP 8.5, append --ignore-platform-req=php+ to the Composer command to ignore only PHP upper bounds while retaining minimum versions and extension requirements.

See RELEASING.md for the branch policy, checks, and commands to publish beta and stable versions.

For testing

docker compose run --rm php vendor/bin/phpstan --memory-limit=2G

Configure application listener discovery

The package does not assume a vendor location or an application namespace. Add a configured provider to your application's config aggregator:

new \Jield\ApiTools\ListenerConfigProvider([
    'MyApp\\Api\\Rest' => __DIR__ . '/Rest',
]),

Each key is an autoloadable namespace prefix and each value is its listener directory. Multiple namespaces/directories are supported. The application owns these paths, so Composer symlinks and Docker mounts work without package changes.

Reuse Entra authentication

The package registers EntraJwksProvider, EntraTokenValidator, EntraOnBehalfOfClient, and TokenExchange as services. Implement EntraResourceInterface and EntraClientInterface on your configuration object or an adapter; the package has no dependency on application entities or user services.

Configure api-tools-oauth2.entra.application_roles with the roles your app accepts for application tokens. An empty list rejects application tokens. diagnostics defaults to false; enable detailed validation errors explicitly for development. cache_directory optionally selects the JWKS cache location. Token lifetime and issuer use the existing api-tools-oauth2.access_lifetime and api-tools-oauth2.options.issuer settings.

Extend AbstractEntraBearerAuthenticationListener to select supported routes and map a validated token to an identity. Its createIdentity hook receives unverified claims only to select trusted local configuration; it must call the validator before creating an identity. The package handles bearer parsing, rejection responses, event identity registration and nonpersistent authentication storage. The application attaches its listener to MvcAuthEvent::EVENT_AUTHENTICATION at its chosen priority.

Use EntraIdentity::isApplication() and EntraIdentity::scopes($claims, $applicationRoles) only after validation to interpret the principal and permissions. Delegated permissions come from scp; application permissions are restricted to the consuming application's explicit role allow list, which defaults to empty. EntraIdentity::applicationSubject() formats stable spn:TENANT_ID:OBJECT_ID subjects. The consuming application owns its permission catalogue and policy.

Implement TokenExchangeInterface for application client/user authorization and persistence, then inject it into OAuth2\Controller\TokenExchangeController. Use TokenExchange::validateRequest() and issueToken() for RFC request checks, signed access tokens and source expiry limits. Exchanges do not issue refresh tokens. Normal OAuth authorization and refresh grants remain in the package server factories; refresh rotation uses the existing OAuth options.

Run package tests with vendor/bin/phpunit. When developing against an application's installed dependencies:

API_TOOLS_TEST_AUTOLOAD=../solodb/vendor/autoload.php ../solodb/vendor/bin/phpunit -c phpunit.xml

Declare listener scope policies

AbstractRoutedListener exposes scope metadata alongside routing and pagination:

protected static array $scopes = ['GET' => ['Example.Read'], 'PATCH' => ['Example.Write']];
protected static array $applicationScopes = []; // Optional scope allow list for application identities.
protected static bool $requireAccessToken = false; // Set true to reject session-only/guest requests too.

Each method lists accepted alternatives. Matching is exact and case-sensitive, with no implicit write-to-read or wildcard grants. HEAD falls back to GET unless explicitly declared. An empty method policy denies bearer access. The getters are getScopes(), getScopesForMethod(), getApplicationScopes() and requiresAccessToken().

Extend OAuth2\AbstractListenerScopeGuard, implement validateIdentity(MvcEvent $event, array $identity): ?Response, and attach it to MvcEvent::EVENT_ROUTE after authentication and HTTP method overrides, before application permissions (for example priority -52 with the package defaults). Register the application's concrete guard in its service manager; the package does not automatically enable it for existing consumers.

The guard rejects expired identities, matches method scopes, restricts application identities and returns JSON OAuth errors with WWW-Authenticate and Cache-Control: no-store. Application identities use principal_type: application; override isApplicationIdentity() when an application uses another claim convention. Application access must match both the method policy and $applicationScopes. Identity validation handles local users, tenant policy and revocation; permission checks must still run afterward. Keep domain permission names and descriptions in the consuming application.

Requests without an AuthenticatedIdentity retain existing session/guest authorization unless a listener sets $requireAccessToken. Authenticated token identities are restricted to declared listener policies and cannot access other controller routes. OPTIONS is left to existing routing/CORS behavior. Attaching this guard is an explicit migration: assign endpoint permissions to existing clients and acquire new tokens before enforcing it in production.