Two-Factor Confirmation
AuthKit provides a step-up two-factor confirmation flow for already-authenticated users who must confirm their identity again before accessing a sensitive page or performing a protected action.
This flow is different from the login-time two-factor challenge.
It is controlled primarily by:
authkit.confirmations.enabled
authkit.confirmations.two_factor.enabled
authkit.confirmations.session
authkit.confirmations.ttl_minutes.two_factor
authkit.confirmations.routes
authkit.auth.guard
authkit.two_factor.driver
authkit.two_factor.columns.enabledOverview
Two-factor confirmation is part of AuthKit’s authenticated confirmation system.
It is used when a user is already signed in, but must provide a fresh two-factor code before continuing.
Typical use cases include:
- accessing high-sensitivity settings pages
- confirming a dangerous account action
- protecting recovery code pages
- enforcing step-up security for privileged operations
At a high level, the flow works like this:
- the user tries to access a protected page or action
- middleware checks whether a fresh two-factor confirmation already exists
- if not, AuthKit stores the intended destination in session
- the user is redirected to the two-factor confirmation page
- the user submits a valid authentication code
- AuthKit verifies the code using the active two-factor driver
- AuthKit stores a fresh confirmation timestamp in session
- the user is redirected back to the intended destination or fallback route
Middleware Protection
Route protection is handled by:
Xul\AuthKit\Http\Middleware\RequireTwoFactorConfirmationMiddlewareThis middleware is responsible for enforcing a fresh two-factor confirmation before the request can continue.
What it checks
The middleware checks:
- authkit.confirmations.enabled
- authkit.confirmations.two_factor.enabled
- authkit.auth.guard
- authkit.confirmations.session.two_factor_key
- authkit.confirmations.ttl_minutes.two_factor
- authkit.confirmations.session.intended_key
- authkit.confirmations.session.type_key
- authkit.confirmations.routes.two_factor
Middleware behavior
When a request enters the middleware:
- AuthKit confirms that the confirmation system is enabled
- AuthKit confirms that two-factor confirmation is enabled
- AuthKit resolves the authenticated user using the configured guard
- AuthKit checks whether a fresh two-factor confirmation timestamp exists in session
- if confirmation is still fresh, the request continues
- otherwise, AuthKit stores:
- the intended destination URL
- the confirmation type
- AuthKit redirects the user to the configured two-factor confirmation page
Freshness window
Freshness is determined using:
- authkit.confirmations.session.two_factor_key
- authkit.confirmations.ttl_minutes.two_factor
If the stored timestamp is older than the configured TTL, the confirmation is considered stale and a new confirmation is required.
Users without two-factor enabled
The middleware also includes support for redirecting users who do not yet have two-factor enabled to the authenticated two-factor settings page.
That redirect uses:
authkit.route_names.web.two_factor_settingsand is implemented by:
Xul\AuthKit\Http\Middleware\RequireTwoFactorConfirmationMiddleware::redirectToTwoFactorSetup()At the moment, that check is present in the middleware but commented out in the provided implementation.
If enabled in your own flow, it allows you to require that users first enable two-factor before they can access the protected area.
Controller Flow
Two-factor confirmation submissions are handled by:
Xul\AuthKit\Http\Controllers\Api\App\Confirmations\ConfirmTwoFactorControllerThis controller is intended for already-authenticated users and remains intentionally thin.
Responsibilities
The controller:
- resolves the authenticated user from authkit.auth.guard
- validates the request through Xul\AuthKit\Http\Requests\App\Confirmations\ConfirmTwoFactorRequest
- builds the normalized mapped payload for the confirm_two_factor context
- delegates verification to Xul\AuthKit\Actions\App\Confirmations\ConfirmTwoFactorAction
- returns JSON for API or AJAX consumers
- returns redirects for standard web submissions
Response behavior
The controller uses:
Xul\AuthKit\Support\Resolvers\ResponseResolverto determine whether the response should be JSON or redirect-based.
For redirect responses, it resolves the fallback confirmation page from:
authkit.route_names.web.confirm_two_factorValidation
Validation is handled by:
Xul\AuthKit\Http\Requests\App\Confirmations\ConfirmTwoFactorRequestThe request is schema-aware and driven by configuration.
Relevant config keys:
authkit.schemas.confirm_two_factor
authkit.validation.providers.confirm_two_factorDefault behavior
The default confirm-two-factor form is built around the authentication code field.
The request ensures the confirmation input is present and valid for the schema context.
Custom validation provider
If you want to override the default validation behavior, you may configure:
authkit.validation.providers.confirm_two_factorAny custom rules provider must implement:
Xul\AuthKit\Contracts\Validation\RulesProviderContractForm Schema
The two-factor confirmation form is defined by:
authkit.schemas.confirm_two_factorThis schema controls:
- the fields rendered on the confirmation page
- labels
- field types
- placeholders
- UI metadata
- submit button text
By default, this is a confirmation form for submitting an authentication code, not a login-time challenge form.
That distinction matters:
confirm_two_factoris for authenticated step-up confirmationtwo_factor_challengeis for login-time authentication before a session is established
Because the flow is schema-driven, UI and validation stay aligned when the form is customized.
Payload Mapping
After validation, AuthKit builds the normalized payload using:
Xul\AuthKit\Support\Mappers\MappedPayloadBuilderfor the mapper context:
confirm_two_factor
This context is configured under:
authkit.mappers.contexts.confirm_two_factorIf you override the mapper, your custom mapper must implement:
Xul\AuthKit\Contracts\Mappers\PayloadMapperContractAs with other AuthKit confirmation flows, the action remains persistence-aware even though the packaged mapper does not persist confirmation fields by default.
Action Flow
The actual confirmation logic is handled by:
Xul\AuthKit\Actions\App\Confirmations\ConfirmTwoFactorActionThis action is the source of truth for the step-up two-factor confirmation outcome.
Responsibilities
The action:
- ensures an authenticated user object exists
- ensures the user has two-factor enabled
- reads the submitted code from the mapped payload
- resolves the active two-factor driver
- verifies the submitted code
- persists mapper-approved attributes when supported
- stores a fresh two-factor confirmation timestamp in session
- clears transient confirmation navigation metadata
- resolves the correct redirect target
- returns a standardized
Xul\AuthKit\DataTransferObjects\Actions\AuthKitActionResult
Two-factor enabled check
The action determines whether the current user has two-factor enabled by:
- calling
hasTwoFactorEnabled()on the user model when available - otherwise reading the configured enabled column from:
authkit.two_factor.columns.enabled
If the user does not have two-factor enabled, the action returns a failure result and redirects to the two-factor settings page using:
authkit.route_names.web.two_factor_settingsDriver verification
The action resolves the active driver through:
Xul\AuthKit\Support\TwoFactor\TwoFactorManagerusing the configured driver name from:
authkit.two_factor.driverIt then calls the active driver’s verification method against the submitted code.
This means the confirmation flow is driver-aware and not limited to one specific 2FA implementation.
Session Persistence
On successful confirmation, the action writes a fresh session timestamp using:
authkit.confirmations.session.two_factor_key
It also clears:
authkit.confirmations.session.intended_key
authkit.confirmations.session.type_keyThis allows the middleware to recognize that the user has already completed a fresh two-factor confirmation and prevents repeated prompts until the configured TTL expires.
Redirect Resolution
After a successful confirmation, redirect resolution follows this order:
- the stored intended URL from:
authkit.confirmations.session.intended_key- the configured fallback route from:
authkit.confirmations.routes.fallbackIf confirmation fails, the action redirects back to the configured two-factor confirmation page:
authkit.route_names.web.confirm_two_factorIf the user does not have two-factor enabled, the action redirects to:
authkit.route_names.web.two_factor_settingsRelationship to Login-Time Two-Factor
It is important not to confuse this flow with the login-time challenge flow.
Step-up two-factor confirmation
This flow is for:
- already-authenticated users
- sensitive pages and actions
- short-lived confirmation freshness stored in session
Main classes:
Xul\AuthKit\Http\Middleware\RequireTwoFactorConfirmationMiddleware
Xul\AuthKit\Http\Controllers\Api\App\Confirmations\ConfirmTwoFactorController
Xul\AuthKit\Actions\App\Confirmations\ConfirmTwoFactorActionLogin-time two-factor challenge
That separate flow is for:
- users who are not yet fully logged in
- completion of the primary authentication process
- pending login challenges
Main classes there are different and should not be mixed into this step-up confirmation flow.
Key Configuration
The most important configuration entries for this flow are:
authkit.confirmations.enabled
authkit.confirmations.two_factor.enabled
authkit.confirmations.session.two_factor_key
authkit.confirmations.session.intended_key
authkit.confirmations.session.type_key
authkit.confirmations.ttl_minutes.two_factor
authkit.confirmations.routes.two_factor
authkit.confirmations.routes.fallback
authkit.auth.guard
authkit.two_factor.driver
authkit.two_factor.columns.enabled
authkit.route_names.web.confirm_two_factor
authkit.route_names.web.two_factor_settings
authkit.schemas.confirm_two_factor
authkit.validation.providers.confirm_two_factor
authkit.mappers.contexts.confirm_two_factorExample Configuration
'confirmations' => [
'enabled' => true,
'session' => [
'password_key' => 'authkit.confirmed.password_at',
'two_factor_key' => 'authkit.confirmed.two_factor_at',
'intended_key' => 'authkit.confirmation.intended',
'type_key' => 'authkit.confirmation.type',
],
'ttl_minutes' => [
'password' => 15,
'two_factor' => 10,
],
'routes' => [
'password' => 'authkit.web.confirm.password',
'two_factor' => 'authkit.web.confirm.two_factor',
'fallback' => 'authkit.web.dashboard',
],
'two_factor' => [
'enabled' => true,
],
],And the two-factor system it depends on may include:
'two_factor' => [
'enabled' => true,
'driver' => 'totp',
'columns' => [
'enabled' => 'two_factor_enabled',
'secret' => 'two_factor_secret',
'recovery_codes' => 'two_factor_recovery_codes',
'methods' => 'two_factor_methods',
'confirmed_at' => 'two_factor_confirmed_at',
],
],Overrides and Customization
You can customize this flow through configuration without replacing the whole system.
Common override points include:
- validation provider:
authkit.validation.providers.confirm_two_factor- mapper:
authkit.mappers.contexts.confirm_two_factor.class- confirmation page controller:
authkit.controllers.web.confirm_two_factor- confirmation action controller:
authkit.controllers.api.confirm_two_factor- active two-factor driver:
authkit.two_factor.driverAny custom rules provider must implement:
Xul\AuthKit\Contracts\Validation\RulesProviderContractAny custom mapper must implement:
Xul\AuthKit\Contracts\Mappers\PayloadMapperContractAny custom two-factor driver must implement:
Xul\AuthKit\Contracts\TwoFactorDriverContractBest Practices
- Use two-factor confirmation only for pages and actions that are genuinely sensitive.
- Do not apply
Xul\AuthKit\Http\Middleware\RequireTwoFactorConfirmationMiddlewareto the confirmation page itself. - Keep
authkit.confirmations.ttl_minutes.two_factorshort enough to provide real security value, but not so short that it causes unnecessary friction. - Make sure the authenticated user model can accurately report whether two-factor is enabled, either through
hasTwoFactorEnabled()or the configured enabled column. - Use the two-factor settings route as the fallback destination when step-up confirmation is required but the user has not yet enabled two-factor.
- Prefer extending this flow through configuration, drivers, mappers, and validation providers instead of modifying AuthKit internals directly.