Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 202
Calteran Avatar asked Sep 08 '25 09:09

Calteran


1 Answers

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.

like image 120
ndm Avatar answered Sep 10 '25 05:09

ndm