How to Use API-token-authenticator to Secure Your CakePHP 4 REST APIs

CakePHP ApiTokenAuthenticator Plugin

A Simple Token Authentication Plugin for CakePHP 4 REST API-s.

Configuration

Users table

In your users table you should have a field named token, or whatever name you choose for the token. We will use token in the examples. The token value will not be automatically generated by the plugin. You can generate it in your UsersController.php file's login() method (or elsewhere if you want). See the example below.

Changing the default settings

If you are happy with the default settings, you can skip this section.

For defaults see config/apiTokenAuthenticator.php file in the plugin's directory.

If you want to change any of the values then create your own config/apiTokenAuthenticator.php file at your project's config directory. In your config file, you should use only those keys that you want to change. It will be merged to the default one. So, for example, if you are happy with all the options, except in your case the token's header name is Authorization, then you have to put this into your on config file.

<?php
return [
  'ApiTokenAuthenticator' => [
    'header' => 'Authorization',
  ]
];

Authentication

The plugin authentication workflow is the following.

At your client appliacation you should send a POST request to /users/login.json (or what you set in your config/apiTokenAuthenticator.php file) with a JSON object like this.

{
  "email": "rrd@webmania.cc",
  "password": "rrd"
}

If the login was successful than you will get a response like this.

{
  "user": {
    "id": 1,
    "token": "yourSecretTokenComingFromTheDatabase"
  }
}

Than you can use this token to authenticate yourself for accessing urls what requires authentication. The token should be sent in a request header named Token (or what you set in your config/apiTokenAuthenticator.php file).

Installation

1. Install the plugin

Including the plugin is pretty much as with every other CakePHP plugin:

composer require rrd108/api-token-authenticator

Then, to load the plugin either run the following command:

bin/cake plugin load ApiTokenAuthenticator

or manually add the following line to your app's src/Application.php file's bootstrap() function:

$this->addPlugin('ApiTokenAuthenticator');

2. Disable CSRF protection

You should comment out CsrfProtectionMiddleware.

3. Load the plugin's components

At your AppController.php file's initialize() function you should include these components:

public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('RequestHandler');
    $this->loadComponent('Authentication.Authentication');
}

4. Set password hasher

Update your src/Model/Entity/User.php file adding the following.

use Authentication\PasswordHasher\DefaultPasswordHasher;
protected function _setPassword(string $password)
  {
    $hasher = new DefaultPasswordHasher();
    return $hasher->hash($password);
  }

5. Set extensions for routes

As you probably will use JSON urls, do not forget to add this lien to your routes.php file.

$builder->setExtensions(['json']);

That's it. It should be up and running.

The login() method

If you use static tokens

Login method is not added automatically, you should implement it. Here is an example how.

public function login()
{
    $result = $this->Authentication->getResult();
    if ($result->isValid()) {
        $userIdentity = $this->Authentication->getIdentity();
        $user = [
            'id' => $userIdentity->id,
            'token' => $userIdentity->token
        ];
        $this->set(compact('user'));
        $this->viewBuilder()->setOption('serialize', ['user']);
    }
}

If you use dynamic tokens

public function login()
{
    $result = $this->Authentication->getResult();
    if ($result->isValid()) {
        $userIdentity = $this->Authentication->getIdentity();

        $user = $userIdentity->getOriginalData();
        $user->token = $this->generateToken();
        $user = $this->Users->save($user);
        $user = $this->Users->get($user->id);

        $this->set(compact('user'));
        $this->viewBuilder()->setOption('serialize', ['user']);
    }
    // if login failed you can throw an exception, suggested: rrd108/cakephp-json-api-exception
}

private function generateToken(int $length = 36)
{
    $random = base64_encode(Security::randomBytes($length));
    $cleaned = preg_replace('/[^A-Za-z0-9]/', '', $random);
    return $cleaned;
}

Token expiration

By default tokens are not invalidated by the plugin, you can use them permanently or as long as there is no new login session like in the example code above.

If you want the plugin to use tokens only for a certain period of time, you should do the following steps.

Add a column to your users table named token_expiration and set it's type to datetime. You can use a different field name, but you have to change it in the following steps.

In your config/apiTokenAuthenticator.php file set 'tokenExpiration' => 'token_expiration'.

Update your src/Model/Entity/User.php file adding the field to the $accessible array.

protected $_accessible = [
  'email' => true,
  // your other fields here
  'token' => true,
  'token_expiration' => true,
];
  • Update your src/Model/Table/UsersTable.php file adding the following.
$validator
  ->dateTime('token_expiration')
  ->allowEmptyDateTime('token_expiration');
  • In your src/Controller/UsersController.php file you should modify login() method.
public function login()
{
  $result = $this->Authentication->getResult();
  if ($result->isValid()) {
      $userIdentity = $this->Authentication->getIdentity();
      $user = $userIdentity->getOriginalData();
      list($user->token, $user->token_expiration) = $this->generateToken();
      $user = $this->Users->save($user);

      $this->set(compact('user'));
      $this->viewBuilder()->setOption('serialize', ['user']);

      // delete all expired tokens
      $this->Users->updateAll(
          ['token' => null, 'token_expiration' => null],
          ['token_expiration <' => Chronos::now()]
      );
  }
}

private function generateToken(int $length = 36, string $expiration = '+6 hours')
{
  $random = base64_encode(Security::randomBytes($length));
  $cleaned = preg_replace('/[^A-Za-z0-9]/', '', $random);
  return [$cleaned, strtotime($expiration)];
}

Access without authentication

If you want to let the users to access a resource without authentication you should state it in the controller's beforeFilter() method. The login, register methods are good candidates to allow unauthenticated access.

// For example in UsersController.php
public function beforeFilter(\Cake\Event\EventInterface $event)
{
  parent::beforeFilter($event);
  $this->Authentication->allowUnauthenticated(['login', 'index']);
}

This will allow users to access /users.json url without authentication.

Migration

Migration form version 0.1

Version 0.3 and 0.2 is totally backward compatible with version 0.1

By default, now we use CakePHP's default password hashing instead of md5 as it was less secure. Inspite of this your current users will be able to login with their current password, but if you want to use the more secure hasing for new users and keep old users as they are, you have to do the following.

Make sure in your database the password field is at least 60 characters long.

Update your src/Model/Entity/User.php file adding the following. By this whenever and old user with and md5 hashed password updates his/her password it will be hashed with the default hashing algorythm.

use Authentication\PasswordHasher\DefaultPasswordHasher;
protected function _setPassword(string $password)
  {
    $hasher = new DefaultPasswordHasher();
    return $hasher->hash($password);
  }
  • In your config/apiTokenAuthenticator.php file you should define this passwordHasher array.
return [
  'ApiTokenAuthenticator' => [
    // any other custom settings
    // ...
      'passwordHasher' => [
        'className' => 'Authentication.Fallback',
        'hashers' => [
          'Authentication.Default', [
            'className' => 'Authentication.Legacy',
            'hashType' => 'md5',
            'salt' => false
          ],
        ]
    ]
  ]
];

Download Details:

Author: rrd108
Source Code: https://github.com/rrd108/api-token-authenticator 
License: MIT license

#php #cakephp #token #authentication #api 

How to Use API-token-authenticator to Secure Your CakePHP 4 REST APIs
1.15 GEEK