Authorization
Authorization answers the question "what is this user allowed to do": anyone may log in, but only the author should edit an article. In CakePHP permissions are described with policies, classes where each action has a method returning true or false. They are handled by the Authorization plugin.
Installation
composer require "cakephp/authorization:^3.0"
bin/cake plugin load AuthorizationThe plugin relies on the login from the previous lesson. Articles must have a user_id column with the author's number: add it with a migration and fill it when an article is created: $article->user_id = $this->request->getAttribute('identity')->getIdentifier();.
Setup
In src/Application.php add a second interface and a method:
// src/Application.php
use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Policy\OrmResolver;
public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
return new AuthorizationService(new OrmResolver());
}AuthorizationServiceProviderInterface is added to the implements list of the class. OrmResolver finds a policy by the entity class: ArticlePolicy for Article.
In the middleware() method add a line after AuthenticationMiddleware:
->add(new AuthorizationMiddleware($this))And load the component in AppController::initialize():
$this->loadComponent('Authorization.Authorization');A policy
Policies live in the src/Policy folder:
bin/cake bake policy --type entity Article// src/Policy/ArticlePolicy.php
namespace App\Policy;
use App\Model\Entity\Article;
use Authorization\IdentityInterface;
class ArticlePolicy
{
public function canEdit(IdentityInterface $user, Article $article): bool
{
return $article->user_id === $user->getIdentifier();
}
}The canEdit() method is responsible for the edit action: only the author may edit an article. For deleting you write canDelete(), and so on.
Checking in an action
// src/Controller/ArticlesController.php
public function edit(int $id)
{
$article = $this->Articles->get($id);
$this->Authorization->authorize($article);
// the usual form handling follows
}authorize() calls the policy method named after the current action. Log in as another user and open someone else's article for editing: a 403 error appears instead of the form.
Actions without a check
The plugin requires every action either to check permissions or to skip the check explicitly:
public function index(): void
{
$this->Authorization->skipAuthorization();
$this->set('articles', $this->paginate($this->Articles->find()));
}Checking without an error
| Where | Call |
|---|---|
| In an action | $this->Authorization->can($article, 'edit') |
| In a template | $this->request->getAttribute('identity')->can('edit', $article) |
Both calls return true or false. This is how a template hides the "Edit" link from those who cannot use it:
<p>
<?php if ($this->request->getAttribute('identity')->can('edit', $article)): ?>
<?= $this->Html->link('Edit', ['action' => 'edit', $article->id]) ?>
<?php endif; ?>
</p>Common mistake
An action with neither authorize() nor skipAuthorization() ends with an exception saying that authorization was not checked. This is protection from forgotten checks, not a breakage: add one of the two calls to the action.