Skip to content

Feature Request: Add allowUnauthorized() to AuthorizationComponent for parity with Authentication plugin #339

Description

@alphp

Description

Currently, the Authentication plugin provides a very convenient $this->Authentication->allowUnauthenticated(['action']) method to skip authentication checks for specific controller actions.

However, the Authorization plugin lacks a direct equivalent, forcing developers to manually call $this->Authorization->skipAuthorization() inside every public action or build custom logic in beforeFilter.

I propose adding an allowUnauthorized() method to AuthorizationComponent to bring API parity between both core plugins and streamline the handling of public/unauthorized actions.

Proposed Solution

We can introduce an internal tracker for unauthorized actions within the component and intercept the authorization checks before they hit the policies.

Here is a working implementation I am currently using by extending the base component:

<?php
declare(strict_types=1);

namespace Authorization\Controller\Component;

use Authorization\Policy\ResultInterface;
use Cake\Controller\Component;

class AuthorizationComponent extends Component 
{
    protected array $unauthorizedActions = [];

    /**
     * Allow specific actions to bypass authorization checks.
     *
     * @param array<string> $actions List of controller actions.
     * @return $this
     */
    public function allowUnauthorized(array $actions): static 
    {
        $this->unauthorizedActions = $actions;

        return $this;
    }

    /**
     * Overriding performCheck to automatically skip authorization for allowed actions
     */
    protected function performCheck(mixed $resource, ?string $action = null, string $method = 'can'): ResultInterface|bool 
    {
        $request = $this->getController()->getRequest();

        if ($action === null) {
             $action = $this->getDefaultAction($request);
        }

        if (in_array($action, ($this->unauthorizedActions)) {
            $this->skipAuthorization();
            return true;
        }

        return parent::performCheck($resource, $action, $method);
    }
}

Example Usage

This allows a much cleaner and intuitive setup in any controller's beforeFilter:

public function beforeFilter(\Cake\Event\EventInterface $event)
{
    parent::beforeFilter($event);
    $this->Authorization->allowUnauthorized([
        'login',
        'logout',
        'verify',
    ]);

    // Global authorization check for the rest of the actions
    if (!$this->Authorization->can($this)) {
        return $this->redirect('/');
    }
}

Why this should be added

  1. API Consistency: Alignment with how AuthenticationComponent::allowUnauthenticated() works.
  2. Cleaner Controllers: Avoids cluttering public actions with repetitive $this->Authorization->skipAuthorization() calls.
  3. Better DX: Centralizes access control rules in beforeFilter right next to authentication rules.

Additional Notes

  • Naming Conventions: I am completely open to suggestions regarding the names of the allowUnauthorized() method and the $unauthorizedActions property if the core team prefers a different naming convention (e.g., allowBypass(), skipActions(), etc.).
  • Pull Request: If the core team values this feature and approves the overall approach, I am more than happy to create and submit the Pull Request along with the necessary test cases.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions