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é.
Để ứng dụng cakephp auth được với JWT token bạn cần cài 2 packagist cakephp/authentication
và firebase/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
Vào file src/Application.php
load plugin Authentication
public function bootstrap(): void
{
parent::bootstrap();
$this->addPlugin('Authentication');
}
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;
}
Thêm middleware authentication và hàm middleware trong file src/Application.php
use Authentication\Middleware\AuthenticationMiddleware;
// Authentication middleware
->add(new AuthenticationMiddleware($this))
Tạo file src/Controller/Api/AppController.php
là AppController
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;
}
}
}
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.
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');
});
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
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');
}
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');
}
}
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
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...