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
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
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
and after send request to server we get list of user in response if our token is valid
Step 4. If you want to remove user you can pass user id on URL as shown in below image
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 🙂