Skip to content

Extension contracts

Each of the package’s extension points is a container-bound interface with a default implementation. Rebind any of them from a service provider’s register() (or boot()) method to replace the behaviour without touching a caller.

Bambamboole\LaravelOidc\Contracts\ScopeRepository is the catalogue of scopes the provider understands.

interface ScopeRepository
{
/** @return Collection<int, Scope> */
public function all(): Collection;
public function find(string $identifier): ?Scope;
/**
* @param Scope[] $requested
* @return Scope[]
*/
public function finalize(array $requested, string $grantType, ClientEntityInterface $client, ?string $userIdentifier = null): array;
}

The default DefaultScopeRepository merges the OIDC scopes (openid, profile, email, address, phone) over any scopes your app registered via Passport::$scopes, and applies the wildcard (*) parity rules during finalize(). Bind your own to change the catalogue:

$this->app->singleton(
\Bambamboole\LaravelOidc\Contracts\ScopeRepository::class,
MyScopeRepository::class,
);

Bambamboole\LaravelOidc\Contracts\ClaimsResolver maps an authenticated user to a ClaimSet.

interface ClaimsResolver
{
public function resolve(Authenticatable $user): ClaimSet;
}

A ClaimSet is constructed from a scope => [claim => value] map; both the id_token builder and the userinfo endpoint call forScopes() on it with the token’s granted scopes, so a claim is only emitted when its scope was granted (null values are dropped). The default is DefaultClaimsResolver. Bind your own:

$this->app->singleton(
\Bambamboole\LaravelOidc\Contracts\ClaimsResolver::class,
AppClaimsResolver::class,
);

Bambamboole\LaravelOidc\Contracts\ExchangePolicy authorizes every RFC 8693 token exchange (and every Oidc::issueScopedToken() call).

interface ExchangePolicy
{
public function authorize(ExchangeRequest $request): ExchangeGrantResult;
}

The default DefaultExchangePolicy enforces audience reciprocity, the target allowlist, scope narrowing, same-subject, and a lifetime cap — see Token exchange for the full rules. authorize() must return an ExchangeGrantResult or throw a League\OAuth2\Server\Exception\OAuthServerException. Rebind it to add tenant checks or a different allowlist source:

$this->app->singleton(
\Bambamboole\LaravelOidc\Contracts\ExchangePolicy::class,
TenantScopedExchangePolicy::class,
);

Bambamboole\LaravelOidc\Contracts\SessionTokenProvider owns the server-side session root token used by the browser-fetch flow.

interface SessionTokenProvider
{
public function currentToken(): ?string;
public function establish(Authenticatable $user): void;
public function forget(): void;
}

The default SessionMintTokenProvider mints the root token on the Login event, re-mints it lazily as it nears expiry, and revokes + clears it on the Logout event. Rebind it to source the root token elsewhere (e.g. an external SSO exchange):

$this->app->singleton(
\Bambamboole\LaravelOidc\Contracts\SessionTokenProvider::class,
MyExternalSsoTokenProvider::class,
);

Bambamboole\LaravelOidc\Contracts\DeviceRecognizer decides whether the current request comes from a device already known for the user — it backs the LoginEvent::isNewDevice() signal in the post-login pipeline.

interface DeviceRecognizer
{
public function isKnown(Authenticatable $user, Request $request): bool;
}

The default NullDeviceRecognizer returns true for every request (every device is treated as known), so isNewDevice() is effectively always false until a real device-recognition release ships. There is no device tracking behind it yet. Bind your own to add it:

$this->app->singleton(
\Bambamboole\LaravelOidc\Contracts\DeviceRecognizer::class,
MyDeviceRecognizer::class,
);