CakePHP 4: allowUnauthenticated() @ plugin / controllers

Hello,

I already asked @ slack, but I didn’t find a solution.

how to use $this->Authentication->allowUnauthenticated(…) method inside plugin / controllers ?
I use successfully outside the plugin on the users controller for login, register and similar methods, but I can not implement within a single plugin, where the methods must be accessed without authorization. It always takes me back to the login page.

dereuromark 13:52 Uhr

from the main controller (App) for example, or using TinyAuth which does it from a central INI file config. (bearbeitet)

13:54 Uhr

e.g. https://github.com/dereuromark/cakephp-sandbox/blob/master/config/auth_allow.ini#L65
if you only use it for this whitelisting, it should work flawless together with the plugins afaik.

We split our complex web app into smaller parts, ie plugins.

The basic App contains only Users MVC, which contains login, register, etc. methods.
Users controller contains:

public function beforeFilter(\Cake\Event\EventInterface $event)
{
    parent::beforeFilter($event);
    $this->Authentication->addUnauthenticatedActions([
        'login',
        'view',
        'forgotPassword',
        'newPassword',
        'confirmEmail',
        'confirmInvitation',
        ]);
    $this->Authorization->skipAuthorization();
}

This work fine!

But we have a few baked cakephp plugins, which are accessed after authentication. We now have a requirement to access certain methods in the plugin without authentication.

For example:

<?php
declare(strict_types=1);

namespace Supervisor\Controller;

class CertificatesController extends AppController
{

    /**
     * @inheritDoc
     */
    public function beforeFilter(\Cake\Event\EventInterface $event)
    {
        parent::beforeFilter($event);
        $this->Authentication->allowUnauthenticated([
            'view',
            'evaluate',
            'approve',
            'comment',
            'download',
            ]);
        $this->Authorization->skipAuthorization();
    }

When I try to access one of these methods, I have a redirect to the login page. I can’t find a reason why.

Note:

The client requested that we remove the redirect from the query parameter.

Application.php

   public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
    {
        $securityHeaders = new SecurityHeadersMiddleware();
        $securityHeaders
            ->setCrossDomainPolicy()
            ->setReferrerPolicy()
            ->setXFrameOptions()
            ->setXssProtection()
            ->noOpen()
            ->noSniff();

    $https = new HttpsEnforcerMiddleware([
            'redirect' => true,
            'statusCode' => 302,
            'headers' => ['X-Https-Upgrade' => 1],
            'disableOnDebug' => false,
        ]);

    $csp = new CspMiddleware([
            'script-src' => [
                'allow' => [
                    'https://kit.fontawesome.com/',
                    'https://code.jquery.com/',
                ],
                'self' => true,
                'unsafe-inline' => true,
                'unsafe-eval' => false,
            ],
            'style-src' => [
                'allow' => [
                    'https://ka-f.fontawesome.com/',
                ],
                'self' => true,
                'unsafe-inline' => true,
            ],
            'font-src' => [
                'allow' => [
                    'https://ka-f.fontawesome.com/',
                ],
                'self' => true,
            ],
            'img-src' => [
                'blob' => false,
                'self' => true,
                'data' => false,
                'allow' => ['*'],
            ],
            'upgrade-insecure-requests' => true,
        ]);

    $middlewareQueue
        // Catch any exceptions in the lower layers,
        // and make an error page/response
        ->add(new ErrorHandlerMiddleware(Configure::read('Error')))
        ->add($https)
        ->add($csp)

        // Handle plugin/theme assets like CakePHP normally does.
        ->add(new AssetMiddleware([
            'cacheTime' => Configure::read('Asset.cacheTime'),
        ]))
        ->add(new RoutingMiddleware($this))
        ->add(new AuthenticationMiddleware($this))
        ->add(new AuthorizationMiddleware($this, [
            //'requireAuthorizationCheck' => false,
            'unauthorizedHandler' => [
                'className' => 'Authorization.CakeRedirect',
                'url' => ['plugin' => false, 'controller' => 'Users', 'action' => 'login'],
                'queryParam' => null,
                'exceptions' => [
                    \Authorization\Exception\MissingIdentityException::class,
                    \Authorization\Exception\ForbiddenException::class,
                ],
            ],
        ]))
        ->add(new RequestAuthorizationMiddleware())
        ->add($securityHeaders);

    return $middlewareQueue;
}

public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $authenticationService = new AuthenticationService([
        'unauthenticatedRedirect' => '/users/login',
        'queryParam' => 'redirect',
    ]);

    // Load identifiers, ensure we check email and password fields
    $authenticationService->loadIdentifier('Authentication.Password', [
        'fields' => [
            'username' => 'email',
            'password' => 'password',
        ],
        'resolver' => [
            'className' => 'Authentication.Orm',
            'finder' => 'active',
        ],
    ]);

    // Load the authenticators, you want session first
    $authenticationService->loadAuthenticator('Authentication.Session');
    // Configure form data check to pick email and password
    $authenticationService->loadAuthenticator('Authentication.Form', [
        'fields' => [
            'username' => 'email',
            'password' => 'password',
        ],
        'loginUrl' => '/users/login',
    ]);

    return $authenticationService;
}

OK, Solved

<?php
declare(strict_types=1);

namespace App\Policy;

use Authorization\Policy\RequestPolicyInterface;
use Cake\Http\ServerRequest;

class RequestPolicy implements RequestPolicyInterface
{
    /**
     * Method to check if the request can be accessed
     *
     * @param \Authorization\IdentityInterface|null Identity
     * @param \Cake\Http\ServerRequest $request Server Request
     * @return bool
     */
    public function canAccess($identity, ServerRequest $request)
    {
        if ($request->getParam('plugin') === 'DebugKit' || $request->getParam('plugin') === 'Supervisor') {
            return true;
        }

        if ($identity && $request->getParam('plugin') === $identity->role) {
            return true;
        }

        if ($request->getParam('plugin') == null) {
            if ($request->getParam('controller') === 'Users') {
                return true;
            }
        }

        return false;
    }
}