CakePHP 3.x MVC Custom Authentication and Authorization
As the author of B and co-inventor of C programming languages, a hacker by reputation and one the leading pioneers of computer science Ken Thompson puts it “When in doubt, use brute force”. Much like Chaos Theory, programming patterns have long evolved bringing order and sanity within the apparent randomness of chaotic complex systems.
MVC Software Design Patterns formulated in early 1970's by Trygve Reenskaug (Norwegian Computer Scientist), later spearheaded by Jim Althoff with the help of others it was introduced in Smalltalk class libraries based on MVC paradigm around 1980's. Since then MVC patterns have steadily derived forming an ideal vanguard for building complex, enterprise application systems that are modular, secure, collaborative and simplified.
CakePHP began in 2005 as PHP based RAD (Rapid Application Development) framework following the footsteps of “Ruby on Rails”. Unlike Laravel, CodeIginiter or Yii which joined the bandwagon later, I was immediately hooked onto it. Almost a decade back, in the stone ages of web application development where seasoned developers were wrestling with writing complex, enterprise applications while still maintaining clean, consistent, modular and future-proof code, CakePHP was the new Elvis in building that never left.
Twelve years later it’s as cutting edge as “Ruby on Rails” offering advanced features like CLI Wrappers, Migrations, ORM (Object Relational Mapping), Validations, CRUD Scaffolding, RESTful API, Security, Authentication, Authorization, ACL’s, Package Management, Testing and the list goes on.
Highlighting this article around custom (API based) authentication and controller based authorization by using CakePHP intrinsic component and class API’s, it’s tempting to gaze around community plugins like OAuth or Social Auth which work on same principles. It’s justified for systems that rely on standard third party authentication systems but these libraries can be less helpful as complexity heats up. Consider a scenario where you have to authenticate from proprietary API systems, that’s where CakePHP authentication and authorization components come into play.
Before you mix authentication with authorization here’s a little clarification as they both are poles apart functionally.
Authentication - is the process of identifying a user or entity with credentials further saving his information in a session storage. Eg. User johndoe logged in with his username and password, the system creates a new session for him on the server.
Authorization - comes post authentication as an added security layer to identify which resources are accessible within the application to a user or entity. Eg. User johndoe is assigned “Operator” role thereby allowing him access to view other users but he cannot create new users.
CakePHP offers three types of authentication FormAuthenticate, BasicAuthenticate and DigestAuthenticate. The first method FormAuthenticate is extensively seen on SaaS or cloud applications making it the ideal way for systems to present users with a login form and letting them authenticate with credentials. CakePHP uses a single parent abstract class for all three types of authentication called BaseAuthenticate which implements EventListenerInterface. A common practice for authentication is comparing a user record in a database table with provided credentials, however, that is not what we’re trying to achieve. A more detailed explanation with examples is already present in the Cake Book - Authentication which I highly recommend reading.
For proprietary API based authentication we’ll use BaseAuthenticate parent class but before that some security caveats. A user login form must be secure against CSRF attacks, enforcing SSL, restricting HTTP methods and form tampering. Fortunately, CakePHP comes with two handy components called Security and Csrf which work transparently on all the forms provided you create your forms using Cake conventions.
Best place to start is within your AppController.php inside a beforeFilter() event where you have to initialize and load these components before any application logic is executed. Loading it like:
public function beforeFilter(Event $event){
parent::beforeFilter($event);
$this->loadComponent('Security');
$this->loadComponent('Csrf');
$this->loadComponent('ProprietaryAPI');
$this->loadComponent('Auth', [
// Controller based authorization
'authorize' => ['Controller'],
// Defaults to users/login but still
'loginAction' => ['controller' => 'Users', 'action' => 'login'],
// If unauthorized access show Unauthorized access error
// @see https://book.cakephp.org/3.0/en/controllers/components/authentication.html#configuring-authorization-handlers
'unauthorizedRedirect' => FALSE,
// Location to redirect after successful login
'loginRedirect' => ['controller' => 'Dashboard', 'action' => 'index'],
// Set this in individual Controller where you want to make an action publicly accessible
'allowedActions' => [],
'authError' => 'Unauthorized access.',
'authenticate' => ['Awesome'],
'storage' => 'Session'
]);
}
Initialize Auth component inside beforeFilter() event which is foremost event in execution chain with default options. Breaking it down in order, only what’s interesting:
- 'authorize' => ['Controller'] - we’re going to authorize resources after authentication with a controller API, later on that.
- 'loginAction' => ['controller' => 'Users', 'action' => 'login'] - redirect unauthenticated actions to Users/login() controller and method.
- 'loginRedirect' => ['controller' => 'Dashboard', 'action' => 'index'] - which action to take after successful authentication.
- 'authenticate' => ['Awesome'] - we’ll be using custom authentication component called AwesomeAuthenticate following Cake conventions.
Ahead of this Security, Csrf and ProprietaryAPI components are also loaded. Now all we have to do is create a new component called AwesomeAuthenticate.php under /src/Auth folder which extends BaseAuthenticate component and overrides abstract method authenticate(Request $request, Response $response) taking two parameters as request and response objects. The custom authentication component looks like this:
_registry->getController();
$controller->ProprietaryAPI->request([
'method' => 'user/authenticate',
'data' => json_encode($request->data),
'headers' => []
]);
/**
* Check if response returns an auth token. If found
* user is authenticated and we can safely log in.
*/
if ($auth_token = $controller->ProprietaryAPI->validate_token()) {
return array_merge($request->data, array('auth_token' => $auth_token));
}
// User is not authenticated
return FALSE;
}
}
Awesome right! We have a fully functional authentication component but with current authentication settings all controllers and actions are blocked. To allow certain controllers and actions without authentication like in UsersController we have to allow login(), logout(), recoverPassword() and signup() we tap into UsersController’s beforeFilter() event and explicitly tell CakePHP with $this->Auth->allow(['/', 'login', 'logout', 'signup', 'recoverPassword']) which will allow only certain unauthenticated or unauthorized actions in UsersController.
Cake components are designed to package logic that may be shared between controllers. You might think why tinker the registry, well, here’s why, a standard Cake component is derived and extended from Cake\Controller\Component but we’re extending from BaseAuthenticate component. The proprietary API component is just a standard Cake component housing all API related interactions and by calling its request() action we’re making a Cake request using core Cake\Network\Http\Client class and returning response. Additionally checking if the authentication token received is valid. Cake expects authenticate() method to return either an array of user data or false if a user could not be authenticated.
From here on if a user tries to access unauthenticated resource he will be redirected to login page with a notice message asking to authenticate first. After the form is submitted with credentials the AwesomeAuthenticate component comes into play, makes an API request and if a valid authentication token was returned than letting the user access his resources.
Bringing us to the last and most important security aspect - authorization post authentication. Cake is already instructed that we need authorization by using Controllers within the application. According to Cake Book ControllerAuthorize calls isAuthorized() on the active controller, and uses the return of that to authorize a user. This is often the most simple way to authorize users. More advanced authorization adapters like ActionsAuthorize and CrudAuthorize are also available as a separate plugin cakephp/acl which can be installed using Composer.
For sake of simplicity lets see how ControllerAuthorize proves useful in assigning permissions to various resources. By loading Auth component ControllerAuthorize is already derived across all application controllers. Note that ControllerAuthorize is extended using BaseAuthorize, just like we extended BaseAuthenticate to create custom authentication component you can create your own authorization component as well. Finally override isAuthorized() action in individual controllers, in this case PostsController as:
public function isAuthorized($user){
// Admin can access every action
if (isset($user['role']) && $user['role'] === 'admin') {
return TRUE;
}
// Default deny
return FALSE;
}
The action is pretty self explanatory, it checks if current user is admin and allows access to certain resources accordingly. Whenever isAuthorized() event is fired it returns the current user in $user parameter variable so you have to build your user data array carefully when it’s returned from AwesomeAuthenticate component.
Here are some useful reads on CakePHP authentication and authorization:
- https://book.cakephp.org/3.0/en/controllers/components/authentication.html#authentication
- https://book.cakephp.org/3.0/en/controllers/components/authentication.html#creating-custom-authentication-objects
- https://book.cakephp.org/3.0/en/controllers/components/authentication.html#authorization
- https://book.cakephp.org/2.0/en/tutorials-and-examples/blog-auth-example/auth.html
- https://github.com/cakephp/acl