jield-webdev / jield-api-tools
Own implementation of API endpoints. This repository is based on the Laminas API tools but includes shared functionality to easily create API endpoints
Requires
- php: ~8.4.0 || ~8.5.0
- ext-fileinfo: *
- ext-openssl: *
- bshaffer/oauth2-server-php: ^1.14.2
- firebase/php-jwt: ^7.2
- guzzlehttp/guzzle: ^7
- jield-webdev/jield-authorize: ^2.1
- laminas/laminas-authentication: ^2.19
- laminas/laminas-config-aggregator: ^1.20
- laminas/laminas-filter: ^2.42
- laminas/laminas-hydrator: ^4.19
- laminas/laminas-inputfilter: ^2.35
- laminas/laminas-mvc: ^3.8
- laminas/laminas-paginator: ^2.22
- laminas/laminas-permissions-rbac: ^3.8
- laminas/laminas-validator: ^2.65
- lm-commons/lmc-cors: ^1.6
- psr/cache: ^1 || ^2 || ^3
- psr/link: ^2.0
- solarium/solarium: dev-master
- swagger-api/swagger-ui: ^5.34
- symfony/cache: ^5.4 || ^6 || ^7
- symfony/finder: ^5.4 || ^6 || ^7
- webmozart/assert: ^1.12
- zircote/swagger-php: ^5
Requires (Dev)
- iteaoffice/testing: ^5.1
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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.