How to redirect from an authenticator to an external authentication endpoint?


I'm writing a web app which I want to have flexible authentication options due to different customers' needs. I'm using the official cakephp/authentication library, but it doesn't have an OpenID Connect Authenticator, so I'm rolling my own.

The problem I'm having is that I can't return a modified Cake\Http\Response object with a redirect header from my Authenticator. AuthenticatorInterface requires the authenticate() method to return a Result object. I'm hoping to not have to modify more than the Authenticator class, because it kind of defeats the purpose of using the library if I'm just going to rewrite it's flow.

class OidcAuthenticator extends AbstractAuthenticator { public function authenticate(ServerRequestInterface $request, ResponseInterface $response) { // non-pertinent code removed for brevity return $this->_requestAuthorization($request, $response); } private function _requestAuthorization(ServerRequestInterface $request, ResponseInterface $response) { $auth_endpoint = $this->getConfig('authorization_endpoint'); $response_type = 'code'; $state = $this->_setState($request, Security::randomString()); $auth_params = [ 'response_type' => $response_type, 'redirect_uri' => 'http://localhost:8080/', 'client_id' => $this->getConfig('client_id'), 'nonce' => $nonce, 'state' => $state, 'scope' => 'openid' ]; $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&'); /* What I want to return */ $response = $response->withHeader('Location', $auth_endpoint); /* What I have to return */ return new Result(null, Result::FAILURE_MISSING_CREDENTIALS); } }

I can make this work with

$request->getAttribute('session')->close(); header('Location: ' . $auth_endpoint, true, 302); exit();

but that seems to violate CakePHP conventions. Ideally, I could embed the new response object into the Result and the Authentication middleware would catch it and emit it instead of the UnauthenticatedException. This is kind of a best-practices question, but hopefully it's specific enough for SO.

TL;DR: How to redirect when you can't return a response object to the middleware queue?


AFAICT there isn't really a recommended practice for now, it might be worth opening an issue over at GitHub for clarification.

You could make use of AuthenticationRequiredException (or UnauthorizedException in earlier versions), it's kinda tangled with stateless authentication, but there's nothing that would stop you from using it. Unlike stateless authenticators you'd have to throw it in the authenticate() method call flow, something like this:

private function _requestAuthorization( ServerRequestInterface $request, ResponseInterface $response ) { // ... $headers = [ 'Location' => $auth_endpoint ]; $body = null; $statusCode = 302; // in CakePHP 4.x / Authentication 2.x throw new \Authentication\Authenticator\AuthenticationRequiredException ( $headers, $body, $statusCode ); // in CakePHP 3.x / Authentication 1.x throw new \Authentication\Authenticator\UnauthorizedException( $headers, $body, $statusCode ); }

This will then be handled in AuthenticationMiddleware::__invoke() accordingly, where the response will be modified and returned.