Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/en/authentication-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,34 @@ option or by calling `disableIdentityCheck` from the controller's `beforeFilter(
``` php
$this->Authentication->disableIdentityCheck();
```

## Redirecting after login

For the common post-login redirect flow, use `redirectAfterLogin()`:

```php
public function login(): ?\Cake\Http\Response
{
$result = $this->Authentication->getResult();

if ($result && $result->isValid()) {
return $this->Authentication->redirectAfterLogin('/home');
}

return null;
}
```

This uses the plugin's validated login redirect target from the current
request when available and falls back to the default you provide.

If you need to inspect the validated target before redirecting, use
`getLoginRedirect()` instead:

```php
$target = $this->Authentication->getLoginRedirect('/home');
return $this->redirect($target);
```

Avoid reading raw `redirect` query string parameters and passing them directly
to the controller's `redirect()` method.
20 changes: 11 additions & 9 deletions docs/en/authenticators.md
Original file line number Diff line number Diff line change
Expand Up @@ -581,8 +581,8 @@ $service->setConfig([
]);
```

Then in your controller's login method you can use `getLoginRedirect()` to get
the redirect target safely from the query string parameter:
Then in your controller's login method you can use
`redirectAfterLogin()` for the common safe post-login redirect flow:

``` php
public function login(): ?\Cake\Http\Response
Expand All @@ -591,19 +591,21 @@ public function login(): ?\Cake\Http\Response

// Regardless of POST or GET, redirect if user is logged in
if ($result->isValid()) {
// Use the redirect parameter if present.
$target = $this->Authentication->getLoginRedirect();
if (!$target) {
$target = ['controller' => 'Pages', 'action' => 'display', 'home'];
}

return $this->redirect($target);
return $this->Authentication->redirectAfterLogin([
'controller' => 'Pages',
'action' => 'display',
'home',
]);
}

return null;
}
```

If you need to inspect the validated target before redirecting, you can still
use `getLoginRedirect()` directly and handle the response yourself. Avoid
passing raw query string parameters to the controller's `redirect()` method.

## Having Multiple Authentication Flows

In an application that provides both an API and a web interface
Expand Down
4 changes: 1 addition & 3 deletions docs/en/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,7 @@ public function login(): ?\Cake\Http\Response
$result = $this->Authentication->getResult();
// If the user is logged in send them away.
if ($result && $result->isValid()) {
$target = $this->Authentication->getLoginRedirect() ?? '/home';

return $this->redirect($target);
return $this->Authentication->redirectAfterLogin('/home');
}
if ($this->request->is('post')) {
$this->Flash->error('Invalid username or password');
Expand Down
19 changes: 10 additions & 9 deletions docs/en/migration-from-the-authcomponent.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,8 @@ $service->setConfig([
]);
```

Then in your controller's login method you can use `getLoginRedirect()` to get
the redirect target safely from the query string parameter:
Then in your controller's login method you can use
`redirectAfterLogin()` for the common safe post-login redirect flow:

``` php
public function login(): ?\Cake\Http\Response
Expand All @@ -298,19 +298,20 @@ public function login(): ?\Cake\Http\Response

// Regardless of POST or GET, redirect if user is logged in
if ($result->isValid()) {
// Use the redirect parameter if present.
$target = $this->Authentication->getLoginRedirect();
if (!$target) {
$target = ['controller' => 'Pages', 'action' => 'display', 'home'];
}

return $this->redirect($target);
return $this->Authentication->redirectAfterLogin([
'controller' => 'Pages',
'action' => 'display',
'home',
]);
}

return null;
}
```

If you need to inspect the validated target before redirecting, you can still
use `getLoginRedirect()` directly and then call `redirect()` yourself.

## Migrating Hashing Upgrade Logic

If your application uses `AuthComponent`’s hash upgrade
Expand Down
19 changes: 19 additions & 0 deletions src/Controller/Component/AuthenticationComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Cake\Controller\Component;
use Cake\Event\EventDispatcherInterface;
use Cake\Event\EventDispatcherTrait;
use Cake\Http\Response;
use Cake\Routing\Router;
use Cake\Utility\Hash;
use Exception;
Expand Down Expand Up @@ -370,6 +371,24 @@ public function getLoginRedirect(array|string|null $default = null): ?string
return $this->getAuthenticationService()->getLoginRedirect($this->getController()->getRequest()) ?? $default;
}

/**
* Redirect after a successful login using the validated login redirect
* target from the current request when available.
*
* This is a convenience wrapper around `getLoginRedirect()` plus the
* controller's redirect method so applications can use the plugin's
* existing safe redirect parsing without manually reading query params.
*
* @param array|string $default Default URL to use when no valid login redirect is available.
* @return \Cake\Http\Response|null
*/
public function redirectAfterLogin(array|string $default = '/'): ?Response
{
$target = $this->getLoginRedirect($default) ?? $default;

return $this->getController()->redirect($target);
}

/**
* Get the Controller callbacks this Component is interested in.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,84 @@ public function testGetLoginRedirect(): void
Configure::delete('App.base');
}

/**
* testRedirectAfterLogin
*
* @return void
*/
public function testRedirectAfterLogin(): void
{
Configure::write('App.base', '/cakephp');
$url = ['controller' => 'Users', 'action' => 'dashboard'];
Router::createRouteBuilder('/')
->connect('/dashboard', $url);

$this->service->setConfig('queryParam', 'redirect');
$request = $this->request
->withAttribute('identity', $this->identity)
->withAttribute('authentication', $this->service)
->withQueryParams(['redirect' => 'ok/path?value=key']);

$controller = new Controller($request);
$registry = new ComponentRegistry($controller);
$component = new AuthenticationComponent($registry);

$response = $component->redirectAfterLogin($url);
$this->assertSame(Router::url('/ok/path?value=key'), $response?->getHeaderLine('Location'));

Configure::delete('App.base');
}

/**
* testRedirectAfterLoginFallsBackToDefaultForAbsoluteUrls
*
* @return void
*/
public function testRedirectAfterLoginFallsBackToDefaultForAbsoluteUrls(): void
{
$url = ['controller' => 'Users', 'action' => 'dashboard'];
Router::createRouteBuilder('/')
->connect('/dashboard', $url);

$this->service->setConfig('queryParam', 'redirect');
$request = $this->request
->withAttribute('identity', $this->identity)
->withAttribute('authentication', $this->service)
->withQueryParams(['redirect' => 'https://evil.example/phish']);

$controller = new Controller($request);
$registry = new ComponentRegistry($controller);
$component = new AuthenticationComponent($registry);

$response = $component->redirectAfterLogin($url);
$this->assertSame('/dashboard', $response?->getHeaderLine('Location'));
}

/**
* testRedirectAfterLoginFallsBackToDefaultForProtocolRelativeUrls
*
* @return void
*/
public function testRedirectAfterLoginFallsBackToDefaultForProtocolRelativeUrls(): void
{
$url = ['controller' => 'Users', 'action' => 'dashboard'];
Router::createRouteBuilder('/')
->connect('/dashboard', $url);

$this->service->setConfig('queryParam', 'redirect');
$request = $this->request
->withAttribute('identity', $this->identity)
->withAttribute('authentication', $this->service)
->withQueryParams(['redirect' => '//evil.example/phish']);

$controller = new Controller($request);
$registry = new ComponentRegistry($controller);
$component = new AuthenticationComponent($registry);

$response = $component->redirectAfterLogin($url);
$this->assertSame('/dashboard', $response?->getHeaderLine('Location'));
Comment thread
dereuromark marked this conversation as resolved.
}

/**
* testAfterIdentifyEvent
*
Expand Down
Loading