Last updated on May 9th, 2021 at 09:13 pm

CakePHP framework is very popular and it was used in many projects. It has its own ORM which is very powerful and has good and flexible routing. If you want a complete list of features of CakePHP framework you can check here. In my last post, I explained how we can create web services in CakePHP 3 you can read this post if you want to learn some basics of creating web services in CakePHP. In this post, I will teach you how to secure your web API’s or services using JWT (JSON Web Token ). I already create a post to secure Slim Framework API using JWT you can read that post in the link below.

Secure Slim PHP API’s using JWT

Now in this post, I will explain how we can implement JWT in CakePHP web service to make or web service more secure. So without wasting any time let get started. You can follow below steps to implement JWT.

Step 1. Hope you already installed CakePHP if not then open a terminal and run below command.

composer create-project --prefer-dist cakephp/app cakeJWT

Step 2. Setup ACL in your project so you can create the various role-based users. To setup ACL please read this post:- Cakephp3 ACL Implementation.

Step 3. If ACL setup is complete then we need to install JWT plugin in our project

composer require admad/cakephp-jwt-auth:2.4.0

Step 4. Now open config/bootstrap.php file and load JWT plugin.

Plugin::load('ADmad/JwtAuth');

Step 5. Now create a new folder inside controller in which we can write our web services you can name that folder whatever you want in my case my folder name is Api.

Step 6. Create a new AppController.php file inside src/Controller/Api/AppController.php to setup Auth component and paste below code inside it.

<?php
namespace App\Controller\Api;

use Cake\Controller\Controller;
use Cake\Event\Event;

class AppController extends Controller
{

    public function initialize()
    {
        parent::initialize();

        $this->loadComponent('RequestHandler');
        $this->loadComponent('Auth', [
            'storage' => 'Memory',
            'authenticate' => [
                'Form' => [
                    'scope' => ['Users.group_id' => 1]
                ],
                'ADmad/JwtAuth.Jwt' => [
                    'parameter' => 'token',
                    'userModel' => 'Users',
                    'fields' => [
                        'username' => 'id'
                    ],
                    'queryDatasource' => true
                ]
            ],
            'unauthorizedRedirect' => false,
            'checkAuthIn' => 'Controller.initialize'
        ]);
    }
}

Step 7. Now create a new file inside Api folder in which we can write our service src/Controller/Api/ApiController.php

<?php
namespace App\Controller\Api;

use Cake\Event\Event;
use Cake\Network\Exception\UnauthorizedException;
use Cake\Utility\Security;
use Firebase\JWT\JWT;
use Cake\Http\ServerRequest;
use Cake\I18n\Time;

class ApiController extends AppController
{

    public function initialize()
    {
        parent::initialize();

    }

    public function beforeFilter(Event $event) {
        parent::beforeFilter($event); 
    }

    /* Your service code here*/
}

Step 8. Now in this step, I can copy paste user controller functions into my ApiController and also add login function to let user login and get access token, to create the user after that my controller looks like something this.

<?php
namespace App\Controller\Api;

use Cake\Event\Event;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Utility\Security;
use Firebase\JWT\JWT;
use Cake\Http\ServerRequest;
use Cake\I18n\Time;

class ApiController extends AppController
{

    public function initialize()
    {
        parent::initialize();
        $this->loadModel('Users');

        $this->Auth->allow(['login', 'add']);

    }

    public function beforeFilter(Event $event) {
        parent::beforeFilter($event); 
    }

    /**
     * Login User to generate token
     */
    public function login()
    {
        $user = $this->Auth->identify();

        if (!$user) {
            throw new UnauthorizedException("Invalid login details");
        }else{
            $tokenId  = base64_encode(32);
            $issuedAt = time();
            $key = Security::salt();
            $this->set([
                'msg' => 'Login successfully',
                'success' => true,
                'user' => $user,
                'data' => [
                    'token' => JWT::encode([
                        'alg' => 'HS256',
                        'id' => $user['id'],
                        'sub' => $user['id'],
                        'iat' => time(),
                        'exp' =>  time() + 86400,
                    ],
                    $key)
                ],
                '_serialize' => ['success', 'data', 'user', 'key']
            ]);
        }
    }

    /**
     * Index method
     *
     * @return \Cake\Network\Response|null
     */
    public function index()
    {
        $this->paginate = [
            'contain' => ['Groups'],'order' => ['id' => 'desc']
        ];
        $users = $this->paginate($this->Users);
        
        $this->set(compact('users'));
        $this->set('_serialize', ['users']);
    }
	

    /**
     * View method
     *
     * @param string|null $id User id.
     * @return \Cake\Network\Response|null
     * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
     */
    public function view($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => ['Groups', 'Addresses']
        ]);

        $this->set('user', $user);
        $this->set('_serialize', ['user', 'groups']);
    }

    /**
     * Add method
     *
     * @return \Cake\Network\Response|void Redirects on successful add, renders view otherwise.
     */
    public function add()
    {
        $user = $this->Users->newEntity();
        if ($this->request->is('post')) {
            $this->request->data['group_id'] = 1;
            $user = $this->Users->patchEntity($user, $this->request->data);
            if ($this->Users->save($user)) {
                $msg['msg'] = 'The user has been saved.';
                $msg['status'] = 1;
            } else {
                $msg['msg'] = 'The user could not be saved. Please, try again.';
                $msg['status'] = 0;
                $msg['error'] = $user->getErrors();
            }
        }

        extract($msg);
        $this->set(compact('error', 'status', 'msg'));
        $this->set('_serialize', ['error', 'status', 'msg']);
    }

    /**
     * Edit method
     *
     * @param string|null $id User id.
     * @return \Cake\Network\Response|void Redirects on successful edit, renders view otherwise.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function edit($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => []
        ]);
        if ($this->request->is(['patch', 'post', 'put'])) {
            $this->request->data['group_id'] = 1;
            $user = $this->Users->patchEntity($user, $this->request->data);
            if ($this->Users->save($user)) {
                $msg['msg'] = 'The user has been saved.';
                $msg['status'] = 1;
            } else {
                $msg['msg'] = 'The user could not be saved. Please, try again.';
                $msg['status'] = 0;
                $msg['error'] = $user->getErrors();
            }
        }

        extract($msg);
        $this->set(compact('error', 'status', 'msg'));
        $this->set('_serialize', ['error', 'status', 'msg']);
    }

    /**
     * Delete method
     *
     * @param string|null $id User id.
     * @return \Cake\Network\Response|null Redirects to index.
     * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
     */
    public function delete($id = null)
    {
        $this->request->allowMethod(['post', 'delete']);
        $user = $this->Users->get($id);
        if ($this->Users->delete($user)) {
            $msg['status'] = 1;
            $msg['msg'] = 'The user has been deleted.';
        } else {
            $msg['status'] = 0;
            $msg = 'The user could not be deleted. Please, try again.';
        }

        extract($msg);
        $this->set(compact('status', 'msg'));
        $this->set('_serialize', ['status', 'msg']);
    }
}

Note in above code I remove $this->redirect method and pass status and message. Now we need to add login function in it so we can generate the token for login. add below code in your ApiController.

public function login()
    {
        $user = $this->Auth->identify();
        if (!$user) {
            throw new UnauthorizedException("Invalid login details");
        }else{
            $tokenId  = base64_encode(32);
            $issuedAt = time();
            $key = Security::salt();
            $this->set([
                'msg' => 'Login successfully',
                'success' => true,
                'user' => $user,
                'data' => [
                    'token' => JWT::encode([
                        'alg' => 'HS256',
                        'id' => $user['id'],
                        'sub' => $user['id'],
                        'iat' => time(),
                        'exp' =>  time() + 86400,
                    ],
                    $key)
                ],
                '_serialize' => ['success', 'data', 'user', 'key']
            ]);
        }
    }

We need to allow login access without token so we can allow its access add below code in initialize() method

$this->Auth->allow(['login']);

Step 9. Now our controller part is done you can create your custom function as per your need. Now we can create routes through we can request for API.

Router::prefix('api', function ($routes) {
    $routes->extensions(['json']);
    Router::connect('/api/login', ['controller' => 'Api', 'action' => 'login', 'prefix' => 'api']);
    Router::connect('/api/list', ['controller' => 'Api', 'action' => 'index', 'prefix' => 'api']);
    Router::connect('/api/add', ['controller' => 'Api', 'action' => 'add', 'prefix' => 'api']);
    Router::connect('/api/edit/:id', ['controller' => 'Api', 'action' => 'edit', 'prefix' => 'api', 'id' => null], ['pass' => ['id']]);
    Router::connect('/api/delete/:id', ['controller' => 'Api', 'action' => 'delete', 'prefix' => 'api', 'id' => null], ['pass' => ['id']]);
    Router::connect('/api/view/:id', ['controller' => 'Api', 'action' => 'view', 'prefix' => 'api', 'id' => null], ['pass' => ['id']]);
    
    $routes->fallbacks('InflectedRoute');
});

Add above code inside your routes files.

That’s it now we can test our code, we can make request these API’s using Postman or you can use any REST client.

Access CakePHP 3 JWT based API

Now we can test our web API’s using REST Client, I am using Postman for testing purpose. I already create an online demo of these API’s if you want to test you can use below link.

Live Demo Link:- https://cake_jwt.trinitytuts.com/ 

Now in the below screenshots, you see how we can create a request and get a response.

Note. Whenever we request for service we can use the header and pass the below value in the header

Accept:application/json
Content-Type:application/json

Step 1. First, we need to create a user so we can use add service as shown in the below image

CakePHP JWT Web Services - PHP

Step 2. Now we create a user we can log in user account so we can checklist of the user which create an account in our system

CakePHP JWT Web Services - PHP

When we send a request if user details are correct we can get or token with users details.

Step 3. Now we can pass the token to get a list of users. Note we need to pass token in the header as shown in below

CakePHP JWT Web Services - PHP

and after send request to server we get list of user in response if our token is valid

CakePHP JWT Web Services Example- PHP

Step 4. If you want to remove user you can pass user id on URL as shown in below image

CakePHP JWT webservices - PHP

You can download this sample project from above download link. If this post is helpful then please like share and comment on our social pages.

Happy Coding 🙂