AssegaiPHP 0.10.0: Configurable Authentication Failures

AssegaiPHP 0.10.0 completes the exception-filter lifecycle and lets browser controllers opt into application-configured login redirects while API controllers retain normal 401 responses.

AssegaiPHP 0.10.0: Configurable Authentication Failures

Authentication failures do not always need the same HTTP response.

A browser visiting a protected dashboard usually expects to reach a login page. An API client calling a protected endpoint expects a 401 Unauthorized response that it can handle directly. Both requests can fail the same access rule, but their response policies are different.

AssegaiPHP 0.10.0 gives that distinction a clear place in the request lifecycle.

The guard still decides whether the request may reach the handler. It raises UnauthorizedException when the current session is not authenticated. A browser controller can then opt into LoginRedirectFilter, while an API controller can leave the filter unapplied and retain Core's normal 401 behavior.

No authentication interceptor is required.

Guard and filter in concert

The protected browser controller declares both parts explicitly:

#[Controller('dashboard')]
#[UseGuards(SessionAuthGuard::class, UnauthorizedException::class)]
#[UseFilters(LoginRedirectFilter::class)]
final class DashboardController
{
}

The flow is intentionally small:

guard -> UnauthorizedException -> LoginRedirectFilter -> configured login route

On an API-only controller, omit UseFilters(LoginRedirectFilter::class). The guard and exception remain the same, but the response stays a 401 without a Location header.

This means access policy does not need browser-specific branches, and API behavior does not change merely because the application also serves HTML pages.

The application owns the login URL

New 0.10 projects put authentication policy and framework-owned session settings in config/auth.php:

<?php

return [
  'authentication' => [
    'loginRedirect' => [
      'url' => '/auth/login',
      'statusCode' => 302,
      'preserveTarget' => true,
      'targetSessionKey' => 'auth.intended_url',
      'excludedPaths' => [],
    ],
  ],
  'session' => [
    'name' => 'assegai_session',
    'cookieLifetime' => 0,
    'cookiePath' => '/',
    'cookieDomain' => '',
    'cookieSecure' => null,
    'cookieHttpOnly' => true,
    'cookieSameSite' => 'Lax',
  ],
];

/auth/login is a scaffold value, not a framework-owned route. Applications must replace it when their public login endpoint uses another path.

The built-in filter remains opt-in. Adding the file does not enable redirects globally. Applications can pass a configured filter instance for controller-specific behavior or implement ExceptionFilterInterface when the built-in options do not satisfy their policy.

Safe intended-target restoration

When enabled, the filter can remember the protected URL that brought the browser to the login page. It stores only safe local GET and HEAD targets before Core closes the session.

Cross-origin, scheme-relative, malformed, unsafe-method, and login-route targets are rejected. Excluding the login route prevents a redirect loop.

After successful credential verification, the login handler retrieves and removes the value in one operation:

$target = $session->pull('auth.intended_url', '/dashboard');
return $response->redirect($target, 303);

The 303 response turns the successful login form POST into a GET. The application should validate the local target again before emitting the redirect, especially if other application code can write to the same session key.

A complete exception-filter lifecycle

The release also completes the underlying filter pipeline:

  • global, controller, and handler filters register reliably
  • class-name filters resolve through dependency injection
  • configured filter instances remain supported
  • handler filters take precedence over controller filters, then global filters
  • the first matching filter is terminal
  • Core emits one response for a handled exception
  • filters run before the request session closes

Those rules apply beyond authentication. They give application-specific exception handling predictable ownership and ordering at every supported scope.

Session login from beginning to end

The Auth package's session strategy still expects the application to load a user. It verifies the submitted credentials, rotates the session identifier, removes the password field from the stored user, and establishes authenticated session state.

Core owns session startup and cookie policy during framework requests. Standalone PHP applications can continue to configure session name and lifetime directly on SessionAuthStrategy.

The public 0.10 authentication guide follows the complete application flow: user lookup, credential verification, guard behavior, browser redirects, intended-target restoration, API 401 responses, and logout.

Upgrading

Install Assegai Console 0.10.2 before upgrading a 0.9 application. Its project updater aligns installed 0.9-line Core, ORM, Events, Auth, Collections, Common, Forms, Util, Validation, and any direct project-local Console requirement to ^0.10.0 in one reviewable update plan. RabbitMQ and Beanstalkd remain on their independent compatible release lines. Existing applications can move their session settings from config/default.php to the dedicated auth file when ready; the old location remains supported.

Use the 0.9.x to 0.10.0 update advisor for the application-specific checklist, or read the Core 0.10.0 upgrade notes for the complete compatibility guidance.