Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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 Authorization

The 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

WhereCall
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.

Thanks for reading!

Donate to the site — we work hard for you.