Quick Start

Install the plugin with composer from your CakePHP Project’s ROOT directory (where the composer.json file is located)

php composer.phar require cakephp/authentication

Version 2 of the Authentication Plugin is compatible with CakePHP 4.

Load the plugin by adding the following statement in your project’s src/Application.php:

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

    $this->addPlugin('Authentication');
}

Getting Started

The authentication plugin integrates with your application as a middleware. It can also be used as a component to make unauthenticated access simpler. First, let’s apply the middleware. In src/Application.php, add the following to the class imports:

use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Identifier\IdentifierInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\Router;
use Psr\Http\Message\ServerRequestInterface;

Next, add AuthenticationServiceProviderInterface to the implemented interfaces on your application:

class Application extends BaseApplication implements AuthenticationServiceProviderInterface

Then update your application’s middleware() method to look like:

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $middlewareQueue->add(new ErrorHandlerMiddleware(Configure::read('Error')))
        // Other middleware that CakePHP provides.
        ->add(new AssetMiddleware())
        ->add(new RoutingMiddleware($this))
        ->add(new BodyParserMiddleware())

        // Add the AuthenticationMiddleware. It should be
        // after routing and body parser.
        ->add(new AuthenticationMiddleware($this));

    return $middlewareQueue;
}

Warning

The order of middleware is important. Ensure that you have AuthenticationMiddleware after the routing and body parser middleware. If you’re having trouble logging in with JSON requests or redirects are incorrect double check your middleware order.

AuthenticationMiddleware will call a hook method on your application when it starts handling the request. This hook method allows your application to define the AuthenticationService it wants to use. Add the following method to your src/Application.php:

/**
 * Returns a service provider instance.
 *
 * @param \Psr\Http\Message\ServerRequestInterface $request Request
 * @return \Authentication\AuthenticationServiceInterface
 */
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $service = new AuthenticationService();

    // Define where users should be redirected to when they are not authenticated
    $service->setConfig([
        'unauthenticatedRedirect' => Router::url([
                'prefix' => false,
                'plugin' => null,
                'controller' => 'Users',
                'action' => 'login',
        ]),
        'queryParam' => 'redirect',
    ]);

    $fields = [
        IdentifierInterface::CREDENTIAL_USERNAME => 'email',
        IdentifierInterface::CREDENTIAL_PASSWORD => 'password'
    ];
    // Load the authenticators. Session should be first.
    $service->loadAuthenticator('Authentication.Session');
    $service->loadAuthenticator('Authentication.Form', [
        'fields' => $fields,
        'loginUrl' => Router::url([
            'prefix' => false,
            'plugin' => null,
            'controller' => 'Users',
            'action' => 'login',
        ]),
    ]);

    // Load identifiers
    $service->loadIdentifier('Authentication.Password', compact('fields'));

    return $service;
}

First, we configure what to do with users when they are not authenticated. Next, we attach the Session and Form Authenticators which define the mechanisms that our application will use to authenticate users. Session enables us to identify users based on data in the session while Form enables us to handle a login form at the loginUrl. Finally we attach an identifier to convert the credentials users will give us into an identity which represents our logged in user.

If one of the configured authenticators was able to validate the credentials, the middleware will add the authentication service to the request object as an attribute.

Next, in your AppController load the Authentication Component:

// in src/Controller/AppController.php
public function initialize()
{
    parent::initialize();

    $this->loadComponent('Authentication.Authentication');
}

By default the component will require an authenticated user for all actions. You can disable this behavior in specific controllers using allowUnauthenticated():

// in a controller beforeFilter or initialize
// Make view and index not require a logged in user.
$this->Authentication->allowUnauthenticated(['view', 'index']);

Building a Login Action

Once you have the middleware applied to your application you’ll need a way for users to login. Please ensure your database has been created with the Users table structure used in tutorial. First generate a Users model and controller with bake:

bin/cake bake model Users
bin/cake bake controller Users

Then, we’ll add a basic login action to your UsersController. It should look like:

// in src/Controller/UsersController.php
public function login()
{
    $result = $this->Authentication->getResult();
    // If the user is logged in send them away.
    if ($result->isValid()) {
        $target = $this->Authentication->getLoginRedirect() ?? '/home';
        return $this->redirect($target);
    }
    if ($this->request->is('post')) {
        $this->Flash->error('Invalid username or password');
    }
}

Make sure that you allow access to the login action in your controller’s beforeFilter() callback as mentioned in the previous section, so that unauthenticated users are able to access it:

// in src/Controller/UsersController.php
public function beforeFilter(\Cake\Event\EventInterface $event)
{
    parent::beforeFilter($event);

    $this->Authentication->allowUnauthenticated(['login']);
}

Next we’ll add a view template for our login form:

// in templates/Users/login.php
<div class="users form content">
    <?= $this->Form->create() ?>
    <fieldset>
        <legend><?= __('Please enter your email and password') ?></legend>
        <?= $this->Form->control('email') ?>
        <?= $this->Form->control('password') ?>
    </fieldset>
    <?= $this->Form->button(__('Login')); ?>
    <?= $this->Form->end() ?>
</div>

Then add a simple logout action:

// in src/Controller/UsersController.php
public function logout()
{
    $this->Authentication->logout();
    return $this->redirect(['controller' => 'Users', 'action' => 'login']);
}

We don’t need a template for our logout action as we redirect at the end of it.

Adding Password Hashing

In order to login your users will need to have hashed passwords. You can automatically hash passwords when users update their password using an entity setter method:

// in src/Model/Entity/User.php
use Authentication\PasswordHasher\DefaultPasswordHasher;

class User extends Entity
{
    // ... other methods

    // Automatically hash passwords when they are changed.
    protected function _setPassword(string $password)
    {
        $hasher = new DefaultPasswordHasher();
        return $hasher->hash($password);
    }
}

You should now be able to go to /users/add and register a new user. Once registered you can go to /users/login and login with your newly created user.

Further Reading