Auth engine overview & seams
Beyond the OIDC protocol layer, the package ships a complete auth engine. All the authentication logic lives in the package — login, registration, password reset, email verification, password confirmation, and multi-factor. Your application fills two kinds of seams: the views to render, and a couple of domain actions.
This mirrors how the OIDC consent flow already works: the package owns the flow, the app provides the page and the user-facing decisions.
The identity guard
Section titled “The identity guard”The engine authenticates against a dedicated session guard, identity (configurable via
oidc.auth.guard). The package registers it automatically if your app hasn’t defined it, backed
by the users provider, and routes the OIDC authorization flow through the same guard, so the
provider and the auth engine share one session.
View seams
Section titled “View seams”Each auth surface renders through a typed view contract — an interface in the
Views namespace of the domain that renders it (for example
Bambamboole\LaravelOidc\Server\Authentication\Views) with a single respond() method that takes a matching
prompt (the page-specific data) and the Request, and returns a Responsable or Response:
interface LoginView{ public function respond(LoginPrompt $prompt, Request $request): Responsable|Response;}Eight contracts cover every auth surface:
| Contract | Prompt | Renders for |
|---|---|---|
LoginView |
LoginPrompt (status, methods) |
Login |
RegisterView |
— (respond(Request $request)) |
Registration |
PasswordResetRequestView |
PasswordResetRequestPrompt (status) |
Password reset request step |
PasswordResetView |
PasswordResetPrompt (token, email, status) |
Password reset reset step |
EmailVerificationView |
EmailVerificationPrompt (status) |
Email verification |
PasswordConfirmationView |
— (respond(Request $request)) |
Password confirmation |
PasswordUpdateView |
PasswordUpdatePrompt (requiresCurrentPassword, expired, status) |
Changing a password |
TwoFactorChallengeView |
TwoFactorChallengePrompt |
Multi-factor challenge |
FactorSetupView |
FactorSetupPrompt (options, required, enrolled, status) |
Factor enrollment |
ConsentView |
ConsentPrompt (client, user, scopes, authToken) |
OAuth consent |
LogoutConfirmationView |
LogoutPrompt (user, client, postLogoutRedirectUri, state, confirmationToken) |
Logout confirmation |
Override one by binding your implementation over the contract, typically in a service provider’s
boot() (a bind there wins over the package’s default, since package providers boot first):
use Bambamboole\LaravelOidc\Server\Authentication\Views\LoginPrompt;use Bambamboole\LaravelOidc\Server\Authentication\Views\LoginView;use Illuminate\Http\Request;
$this->app->bind(LoginView::class, fn () => new class implements LoginView{ public function respond(LoginPrompt $prompt, Request $request) { return view('auth.login', ['status' => $prompt->status]); }});Controllers resolve the contract with app(LoginView::class)->respond($prompt, $request), so a
bound class must be resolvable by the container with no arguments passed to make(); it receives
the real prompt only through respond().
A flow whose contract is not bound throws MissingAuthViewException when that route is hit, so a
headless install fails loudly on the first request to an unbound surface instead of rendering
nothing. Install bambamboole/laravel-oidc-ui to bind all nine contracts at once (see
UI installation), or bind only the ones you enable yourself.
For engine tests that drive the real controllers without a view package installed, add
Bambamboole\LaravelOidc\Server\Testing\FakesAuthViews to the test and call fakeAuthViews() before
hitting a GET route — it binds every contract to a minimal JSON responder (see
Testing).
Action seams
Section titled “Action seams”Three action contracts in Shared\Users and Shared\Brokering let the package stay out of
your user model and persistence. Bind your implementation in a service provider:
use Bambamboole\LaravelOidc\Server\Shared\Users\CreateUser;use Bambamboole\LaravelOidc\Server\Shared\Brokering\CreateUserFromSocialAccount;use Bambamboole\LaravelOidc\Server\Shared\Users\ResetUserPassword;
// Called by the registration flow with the validated input array.$this->app->bind(CreateUser::class, App\Actions\CreateNewUser::class);
// Called by the password-reset flow with the user and validated input.$this->app->bind(ResetUserPassword::class, App\Actions\ResetUserPassword::class);
// Called on first social login when auto_provision is on.$this->app->bind(CreateUserFromSocialAccount::class, App\Actions\CreateUserFromGoogle::class);Each contract is a single __invoke() method: CreateUser(array $input): Authenticatable,
ResetUserPassword(CanResetPassword $user, array $input): void, and
CreateUserFromSocialAccount(SocialUser $socialUser, string $provider): Authenticatable.
A flow whose contract is not bound is disabled: registration answers 404 and social
provisioning fails the login instead of creating a user.
What the app keeps, what the package owns
Section titled “What the app keeps, what the package owns”- The app keeps: its auth views/pages, its
CreateNewUser/ResetUserPassworddomain actions, itsUsermodel, and its config values. - The package owns: the controllers, routes, validation, event dispatch, session regeneration, rate limiting, the MFA machinery, and the post-login pipeline.
The flows
Section titled “The flows”- Login & logout
- Registration
- Password reset & confirmation
- Email verification
- Multi-factor authentication
- The post-login pipeline — the adaptive decision hook and
acr/amr.