Skip to content

Add AuthorizationComponent::skipAuthorizationActions() - #340

Draft
dereuromark wants to merge 2 commits into
3.xfrom
skip-authorization-actions
Draft

Add AuthorizationComponent::skipAuthorizationActions()#340
dereuromark wants to merge 2 commits into
3.xfrom
skip-authorization-actions

Conversation

@dereuromark

@dereuromark dereuromark commented Aug 28, 2026

Copy link
Copy Markdown
Member

Draft, mainly to have something concrete to discuss on #339.

The behavior asked for there mostly exists already: the skipAuthorization config key is handled in authorizeAction() and is documented in docs/en/component.md. Two things were missing.

A fluent setter, so a controller can declare its own public actions instead of centralizing them in AppController:

public function beforeFilter(EventInterface $event)
{
    parent::beforeFilter($event);

    $this->Authorization->skipAuthorizationActions('verifyEmail', 'webhook');
}

Signature and merge semantics mirror authorizeModel(string ...$actions).

Manual checks honoring the list. Previously only the automatic check in authorizeAction() looked at skipAuthorization, so a single gate in beforeFilter() still ran the policy for actions the application had already declared public:

$this->Authorization->skipAuthorizationActions('login', 'logout');

if (!$this->Authorization->can($this)) {
    return $this->redirect('/');
}

can(), canResult() and authorize() now treat the current action as authorized when it is on the list.

Two constraints on that, both covered by tests:

  • Only an implicit check is affected. An explicit action still runs its policy, so can($article, 'delete') is unchanged by delete being on the list. Otherwise a list of public controller actions would silently turn into a list of always-allowed policy verbs, which is not what the config means and would be easy to trip over in a template.
  • The raw action name is matched, through a shared isSkippedAction() helper, so authorizeAction() and the manual checks agree when actionMap is set. Matching the mapped name in one path and the raw name in the other would make mapAction('edit', 'modify') skip in one place and not the other.

canResult() returns a Result, not true, since it is typed : ResultInterface and authorize() calls getStatus() on what it returns.

Docs now describe the three ways to skip and when each fits, per LordSimal's request on the issue.

Open question before this leaves draft: the name. skipAuthorizationActions() matches the config key but is long. Alternatives raised on the issue are allowUnauthorized(), authorizeAction() and authorizeController(). Worth noting #172 and #176 went the other way, collapsing authorizeModel() and mapAction() into a single action config, so a third fluent setter may be the wrong shape regardless of the name.

Refs #339

The `skipAuthorization` config key already marks controller actions as
public, but unlike `authorizeModel()` and `mapAction()` it has no fluent
setter, so it can only be set through `loadComponent()` options or
`setConfig()`.

Add a variadic setter that merges into the existing config, matching the
`authorizeModel(string ...$actions)` signature. `authorizeAction()` runs on
`Controller.startup`, which dispatches after `Controller.initialize`, so
registering actions from `beforeFilter()` takes effect.
@dereuromark dereuromark added this to the 3.x milestone Aug 28, 2026
Listing an action in `skipAuthorization` only affected the automatic check in
`authorizeAction()`. A manual `can()` or `authorize()` for the current action,
for example a single gate in `beforeFilter()`, still ran the policy and failed
on actions the application had already declared public.

Treat the current action as authorized in `can()`, `canResult()` and
`authorize()` when it is on the list. Only an implicit check is affected: an
explicit action such as `can($article, 'delete')` always runs its policy. The
raw action name is matched through the shared `isSkippedAction()` helper, the
same key `authorizeAction()` uses, so both paths agree when `actionMap` is set.

Document the three ways to skip authorization and how they differ.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant