Auth with JWT token cakePHP 4

  • September 4, 2022
  • 633

Khi làm việc với REST API các ứng dụng thường dùng JWT token để auth giữa client với server. Hôm nay chúng ta cùng tìm hiểu làm thể nào để Auth with JWT token cakePHP 4 nhé.

Require cakePHP auth with JWT

Để ứng dụng cakephp auth được với JWT token bạn cần cài 2 packagist cakephp/authenticationfirebase/php-jwt với composer


// Cài đặt cakephp auth
composer require cakephp/authentication:^2.0

// Cài đặt firebase jwt
composer require firebase/php-jwt

Config cakePHP auth with jwt

1. Load plugin Authentication

Vào file src/Application.php load plugin Authentication


public function bootstrap(): void
{
    parent::bootstrap();

    $this->addPlugin('Authentication');
}
2. Get authentication service

Tiếp theo cần định nghĩa authentication service để auth, với api chúng ta sẽ sử dụng JWT token còn với admin chúng ta sử dụng session. Để làm được điều này chúng ta cần thêm hàm getAuthenticationService(), class Application cần implements AuthenticationServiceProviderInterface. Bằng cách thêm code vào file src/Application.php


// Load các service
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Psr\Http\Message\ServerRequestInterface;

// Application implements AuthenticationServiceProviderInterface
class Application extends BaseApplication implements AuthenticationServiceProviderInterface
{

    /**
     * Get authenticationService.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request Request.
     * @return \Authentication\AuthenticationServiceInterface
     */
    public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
    {
        $path = $request->getUri()->getPath();
        $service = new AuthenticationService();
        if (strpos($path, '/api') === 0) {
            $service->loadAuthenticator('Authentication.Jwt', [
                'returnPayload' => false,
            ]);
            $service->loadIdentifier('Authentication.JwtSubject', [
                'resolver' => [
                    'className' => 'Authentication.Orm',
                    'userModel' => 'Users',
                ],
            ]);

            return $service;
        }

        // Web authentication
        // Support sessions and form login.
        $service->setConfig([
            'unauthenticatedRedirect' => '/admin/login',
            'queryParam' => 'redirect',
        ]);

        $fields = [
            'username' => 'email',
            'password' => 'password',
        ];
        // Load the authenticators. Session should be first.
        $service->loadAuthenticator('Authentication.Session');
        $service->loadAuthenticator('Authentication.Form', [
            'fields' => $fields,
            'loginUrl' => '/admin/login',
        ]);

        // Load identifiers
        $service->loadIdentifier('Authentication.Password', [
            'fields' => $fields,
            'resolver' => [
                'className' => 'Authentication.Orm',
                'userModel' => 'Admins',
            ],
        ]);

        return $service;
    }
3. Add Authentication Middleware

Thêm middleware authentication và hàm middleware trong file src/Application.php


use Authentication\Middleware\AuthenticationMiddleware;

// Authentication middleware
->add(new AuthenticationMiddleware($this))
4. Create Api AppController

Tạo file src/Controller/Api/AppController.phpAppController của tất cả api controller


<?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link      https://cakephp.org CakePHP(tm) Project
 * @since     0.2.9
 * @license   https://opensource.org/licenses/mit-license.php MIT License
 */
namespace App\Controller\Api;

use Cake\Controller\Controller;

/**
 * Application Controller
 *
 * Add your application-wide methods in the class below, your controllers
 * will inherit them.
 *
 * @link https://book.cakephp.org/4/en/controllers.html#the-app-controller
 */
class AppController extends Controller
{
    /**
     * User id
     *
     * @var int $userId User id.
     */
    protected $userId;

    /**
     * User
     *
     * @var \App\Model\Entity\User  $device User.
     */
    protected $user;

    /**
     * Initialization hook method.
     *
     * Use this method to add common initialization code like loading components.
     *
     * e.g. `$this->loadComponent('FormProtection');`
     *
     * @return void
     */
    public function initialize(): void
    {
        parent::initialize();

        $this->loadComponent('RequestHandler');
        $this->loadComponent('Authentication.Authentication');
        $this->RequestHandler->renderAs($this, 'json');

        // Parser token
        $result = $this->Authentication->getResult();
        if ($result->isValid()) {
            $data = $result->getData();
            $this->user = $data;
            $this->userId = $data->id;
        }
    }
}

Create JWT token with user

Tạo api {{DOMAIN}}/api/v1/users với method post để đang kí user, tạo JWT token và api {{DOMAIN}}/api/v1/users method get để lấy tất cả users api này require bearer token là jwt token.

1. Define route

Bạn có thể tham khảo hướng dẫn routing cakePHP 4


$routes->scope('/api/v1', ['prefix' => 'Api/V1'], function (RouteBuilder $routes) {
    $routes->setExtensions(['json']);

    // Devices
    $routes->resources('Users');
});
2. Tạo Users model

Tạo user model sửa dụng CLI command. Để chạy lệnh trên bạn cần migrate database trước.


bin/cake bake model Users
3. Tạo file jwt config

Tạo và đăng kí file config/jwt.php để config key và các thông tin khác khi tạo JWT token


<?php

/*
 * Local configuration file to provide for create Jwt token.
 */
return [
    'Jwt' => [
        'key' => env('SECURITY_SALT'),
        'payload' => [
            'iss' => env('APP_NAME'),
        ]
    ]
];

// Load config trong file config/bootstrap.php
/*
 * Load an environment local configuration file to provide overrides to your configuration.
 * Load file config jwt.
 */
if (file_exists(CONFIG . 'jwt.php')) {
    Configure::load('jwt', 'default');
}

4. Tạo Users controller

Tạo usersController dùng CLI command


bin/cake bake controller Users --prefix=Api/V1

Thêm logic xử lý cho api register user và get list user


<?php
declare(strict_types=1);

namespace App\Controller\Api\V1;

use App\Controller\Api\AppController;
use Cake\Core\Configure;
use Firebase\JWT\JWT;
use Cake\Auth\DefaultPasswordHasher;

/**
 * Users Controller
 *
 * @method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = [])
 */
class UsersController extends AppController
{
    /**
     * Initialization hook method.
     *
     * @return void
     */
    public function initialize(): void
    {
        parent::initialize();
        $this->Authentication->allowUnauthenticated(['add']);
    }
    
    /**
     * Index method
     *
     * @return \Cake\Http\Response|null|void Renders view
     */
    public function index()
    {
        $users = $this->paginate($this->Users);

        return $this->success($users->toArray());
    }

    /**
     * Add method
     *
     * @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
     */
    public function add()
    {
        
        $this->request->allowMethod(['post']);

        $user = $this->Users->newEmptyEntity();
        $data = $this->request->getData();
        $user = $this->Users->patchEntity($user, $data);
        $user->password = (new DefaultPasswordHasher())->hash($data['password']);
      
        if ($user->getErrors()) {
            return $this->error($this->getFirstErrorValidate($user->getErrors()));
        }

        if ($this->Users->save($user)) {
            return $this->success(['token' => $this->createJwtToken($user->id)]);
        }

        return $this->error(__('The user could not be saved. Please, try again.'));
    }

    /**
     * Create jwt token.
     *
     * @param int $id Device id.
     * @return string
     */
    private function createJwtToken(int $id): string
    {
        $key = Configure::read('Jwt.key');
        $payload = [
            'iss' => Configure::read('Jwt.payload.iss'),
            'sub' => $id,
        ];

        return JWT::encode($payload, $key, 'HS256');
    }

    /**
     * Response success.
     *
     * @param array $data Data.
     * @param string $message Message.
     * @param int $code Code.
     * @return mixed
     */
    protected function success(array $data = [], $message = 'SUCCESS', $code = 200)
    {
        return $this->response
            ->withStatus($code)
            ->withStringBody(
                (string)json_encode([
                    'status' => true,
                    'data' => $data,
                    'error' => [
                        'code' => $code,
                        'message' => $message,
                    ],
                ])
            );
    }

    /**
     * Response error.
     *
     * @param string $message Message.
     * @param int $code Code.
     * @param array<array|string> $data Data.
     * @return mixed
     */
    protected function error(string $message, int $code = 400, array $data = [])
    {
        return $this->response
            ->withStatus($code)
            ->withStringBody(
                (string)json_encode([
                    'status' => false,
                    'data' => $data,
                    'error' => [
                        'code' => $code,
                        'message' => $message,
                    ],
                ])
            );
    }

    /**
     * Get error valid.
     *
     * @param array $errors error.
     * @return string
     */
    protected function getFirstErrorValidate(array $errors): string
    {
        $key = '';
        $messages = [];
        foreach ($errors as $key => $val) {
            $key = $key;
            $messages = $val;
            break;
        }

        foreach ($messages as $message) {
            return $key . ': ' . strtolower($message);
        }

        return __('Error');
    }
}
5. Kiểm tra lại api

Như vậy là chúng ta đã tạo được JWT token và auth với JWT token. Giờ chỉ việc sử dụng postman test lại ok
Create jwt token
Auth with jwt token
Chúng ta đã thiết lập xong auth with jwt token cakephp 4. Bước đầu tiên để xây dựng các REST API với cakephp 4. Thanks for reading...