Introducing the CakePHP Authorization Plugin

In the next major release of CakePHP we’re going to be removing the AuthComponent. This component and its helpers have been part of CakePHP since the 1.2 days, but their time has come to an end. Over the years, AuthComponent has become a complex and difficult to extend piece of CakePHP. In its wake, we’re promoting two new plugins. First, cakephp/authentication will be replacing the identification parts of AuthComponent while cakephp/authorization handles access control and permissions. These plugins are intended to be used together, but can be used individually. They can also be used outside of CakePHP applications thanks to PSR-7. The new authorization plugin makes answering questions like ‘can the current user edit this record?’ and ‘what records can the current user see?’ simpler to answer from anywhere in your application, not just in controllers.

Installation

The authorization plugin is installed via composer. Currently it is in a beta release, but that will quickly change.

Show Plain Text
  1. # For now run
  2. composer require cakephp/authorization:1.0.0-beta4
  3.  
  4. # Later on run
  5. composer require cakephp/authorization:^1.0

In your application’s bootstrap process you should add the authorization plugin to your application via Plugin::load(). This will enable bake to discover tasks. Next you’ll need to add the middleware to your application:

Show Plain Text
  1. // In your Application.php, import the class.
  2. use Authorization\Middleware\AuthorizationMiddleware;
  3.  
  4. // inside your application's middleware hook.
  5. $middlewareStack->add(new AuthorizationMiddleware($this));

Concepts

Authorization requires a few possibly new concepts.

  • Identity Is a person or agent acting in your application. Generally this will be a user or an API client.
  • Resource A ‘thing’ that an Identity needs to access. For example: Articles, Tags, Comments.
  • Action An operation that an Identity takes on a Resource. For example ‘update’, ‘delete’
  • Policy The logic defining which Actions Identities can take on Resources.

The authorization plugin provides abstractions for Identities, Actions and Policies. The Resources are the domain objects in your application.

Identities

The authorization plugin hooks into your application as middleware. It expects to find an authenticated user in the identity attribute of the request. By default, the authorization middleware will decorate this identity with the Authorization\IdentityDecorator. This interface defines 3 methods, and proxies all other methods, and properties to the decorated object. The decorator adds can() and applyScope() methods. These are the main methods that allow you to check permissions on your resources. Because all access controls are attached to the current user, you can more easily perform access control checks anywhere in your application:

Show Plain Text
  1. // Get the user from the request
  2. $user = $request->getAttribute('identity');
  3.  
  4. // Check a permission
  5. if ($user->can('delete', $article)) {
  6.     echo 'User can delete this thing!';
  7. }

While the decorator approach works, you can avoid any overhead/complexity it adds by implementing the Authorization\IdentityInterface in your existing user class. The documentation has an example of how to do this. Permission checks are always executed by Policy classes.

Policies

Policy classes contain the permissions checking logic in your application. Each resource that you want to check permissions for will need a policy class defined. Policy classes are mapped to the resources in your application by the ‘policy resolver’. For CakePHP applications you can define your policy resolver in your authorization hook method. If your application primarily works with ORM Entities, you can use the following:

Show Plain Text
  1. namespace App;
  2.  
  3. use Authorization\AuthorizationService;
  4. use Authorization\Policy\OrmResolver;
  5. use Cake\Http\BaseApplication;
  6.  
  7. class Application extends BaseApplication
  8. {
  9.     public function authorization($request)
  10.     {
  11.         $resolver = new OrmResolver();
  12.  
  13.         return new AuthorizationService($resolver);
  14.     }
  15. }

If you have multiple kinds of domain objects to check, you can use the ResolverCollection to join resolvers for multiple datasources together.
Now that we have a resolver setup, we can create some policies. The simplest way to do this is with bake:

Show Plain Text
  1. bin/cake bake policy Project

This will generate a class into src/Policy/ProjectPolicy.php. As an example, our application will only allow users to see/edit/delete their own projects. In our policy class we can enforce this logic with the following method:

Show Plain Text
  1. public function canUpdate(IdentityInterface $user, Project $project)
  2. {
  3.     return $user->id == $project->user_id;
  4. }

Policy methods use the convention of can and the action name. We could implement similar logic in our canDelete method too. To show a user their list of projects we can’t check records on individual rows as it would be inefficient to look at all projects to find only those the current user can see. Instead we want to push the access control conditions into the query that creates the project list. We call this a ‘policy scope’ as it scopes a query through a policy. Before we can apply scopes to our queries, we need to create a policy class for our Table class. We can create a table policy with bake:

Show Plain Text
  1. bin/cake bake policy --type Table Projects

This will create src/Policy/ProjectsTablePolicy.php. In it we can add the following method:

Show Plain Text
  1. public function scopeIndex($user, $query)
  2. {
  3.     return $query->where(['Projects.user_id' => $user->id]);
  4. }

Then in our controller endpoints we can use our new policy:

Show Plain Text
  1. // In ProjectsController::index
  2. $user = $this->request->getAttribute('identity');
  3.  
  4. // Apply our policy scope
  5. $query = $user->applyScope('index', $this->Projects->find());
  6.  
  7. $this->set('projects', $this->paginate($query));

Policy scopes should return a new or mutated object. They are ideal to use with query builders and finder logic.

Checking Permission

Earlier we’ve seen a few examples of authorization checks in action. Once loaded the AuthorizationComponent can be used to simplify raising exceptions when authorization checks fail, and skipping authorization on actions that don’t require permissions to be checked.

Show Plain Text
  1. // In a controller
  2. public function view($id)
  3. {
  4.     $project = $this->Projects->get($id)
  5.     // Gets the 'action' from the controller action.
  6.     // Will raise an exception if the check fails.
  7.     $this->Authorization->authorize($project);
  8.  
  9.     // Rest of controller action.
  10. }

If your controller actions don’t require authorization, you can use the skipAuthorization() method to mark an action as not requiring further authorization. By default when authorization fails, or hasn’t been checked the AuthorizationMiddleware will raise an exception that your application can handle. If you’d prefer that authorization errors are converted into redirects, you can do that too. The documentation has more details

Hopefully this illustrates a bit of what the new authorization plugin can do and why we’re removing AuthComponent in favour of more powerful and flexible solutions.

Comments

There is any pattern to create Policy and Policy methods related to prefixed routes, or with route with extensions? Cases like:

/admin/products/update (not the same permissions as /products/update)

/admin/products/view.json (only some user access the json data)

Marcelo on 4/4/18

Right now there aren’t specific policies for prefixed routes. Before going down that path, I would attempt to try and have one policy for each resource. Including rules for all user types/groups in one place makes mistakes harder to make when checking authorization, and lets you look at all the authorization rules at once.

mark story on 7/22/18

Seems very complicated compared to authcomponent. Hope you guys put really effort into documenting and examples, otherwise this will be dedicated turn-off

Developer on 1/24/19

It still does not work after two days. The documentation is also patchy and not as good as it is usually with CakePHP documentation. There is no tutorial or video, very frustrating. In such cases, I keep asking myself: why spend so much time in something that no one can use later …

robogito on 4/17/19

robogito: The documentation is a work in progress. If you open an issue with the problems you’re having we can get the documentation expanded.

mark story on 5/18/19

why do we separate authorization from authentication? can’t we authorize the user without us authenticating the user?
Isn’t it easier to unite the two in one plugin so that it is simpler to make documentation?
I have tried Pluin, but, it still can’t work except using “Request Authorization Middleware”, besides it always fails, even though I follow the document.

Farid Aditya Parma on 6/10/19

Farid: The plugins are separate because we want to give flexibility. While you’re right you can’t really apply authorization without authenticating first, we wanted to give flexibility on how people do authorization. The current plugin reflects how a few of the CakePHP core team folks do authorization in their applications but the policy based model might not fit for every use case, so you can build your own approach as well.

mark story on 7/28/19

Is it possible to use the mapResolver to tie a policy to a query object? IE in your ProjectsTablePolicy class you have that scope accessible in your Projects controller, would it be possible for me to use mapResolver to achieve the same effect? My guess is no since the OrmResolver uses $query->getRepository to find the correct policy. I’m guessing I’d have to write my own Resolver class to achieve such a feat. Also is it possible to have OrmResolver and AuthorizationService working together. My use case is that I find changing things at the query level via policy useful but also want a central policy on the ServerRequest level.

Richard on 9/23/19

Just a bit confused as I have not yet used this.
I have a cakephp 3.x app using ACL Plugin (the whole ACL-ARO-ACO stuff). Using this I am able to control access to each action
Now I am planning to migrate to 4.x. Can Authorization plugin replace ACL plugin ?

Sonik on 2/14/20

Now I am planning to migrate to 4.x. Can Authorization plugin replace ACL plugin ?

I think it can. However, instead of dealing with ARO/ACO objects you have to think in terms of Policys and actions. I think it is a more flexible system as you can encapsulate role based systems and your custom logic for row based access controls all in one place – the Policy class.

mark story on 4/23/20

Thank you mark and sonik, I had the same doubt.

Eric on 7/29/21

Hi Mark!! Nice to write you!! I am using CakePHP 4.2 with the CakePHP Authorization Plugin, of course, and I have a doubt witch I can’t find a solution.

Model: UsersTable.php
Controller: AccountsController.php. It is just a controller, without a model or table in the db called “accounts”. It usethe user model ($this->loadModel(‘Users’);).

Accounts controller manage all stuff from users table. But when i invoke it, got the error “The request to *** did not apply any authorization checks”

My doubt: How can i use the Authorization Plugin in a controller without a model, but related with other models?

Thanks!!!!
PD: Sorry my english. I do my best to make it clear.

Ariel on 9/22/21

This is good. I replaced the ACL with this and its working quite nicely and is faster. One open question, I could not find a way of accessing this in the frontend to hide buttons and links based on access !
In the earlier ACL plugin I used $acl->check() for it but could not find anything similar here

Sonik on 10/26/21

sonik: The user entity should be decorated with a proxy that lets you call @can()@ and check permissions for related entities in your templates.

mark story on 11/23/21

Hi Great Developers I need help and want to understand how can i define dynamic role based permissions in cakephp 4.4.15? May I have to use Policies or I have to use ACL plugin for handle this task? I am very confused.. please let me show some code example how can i define dynamic role and permissions in database table side and admin can create any role and give them permissions post_create,post_view or more.

shaan on 7/29/23

Have your say: