[PHP] Identification basique avec CakePHP 4.x

Si vous utilisez le framework CakePHP et que vous avez dû migrer vers la dernière version pour bénéficier de la compatibilité avec PHP 8.1, vous aurez probablement dû migrer votre système d’identification.

CakePHP : identification basique

Ce petit tutoriel se base sur la documentation officielle, en apportant quelques modifications si vous n’avez pas la même structure au niveau de votre table d’utilisateurs ou de vos contrôleurs.

Installation du plugin

Il faut tout d’abord installer le plugin fourni par CakePHP. Pour cela on utilise l’outil composer.

composer require "cakephp/authentication:^2.0"

Cette version est bien sûr compatible avec la version 4 du framework.

Chargement du plugin

Il faut maintenant éditer le fichier src/Application.php et y ajouter la méthode qui chargera le plugin.

Dans la méthode bootstrap(), après tous les éléments déjà chargés, ajoutez ce bout de code:

$this->addPlugin('Authentication');

Configuration du middleware

Le plugin va permettre de gérer facilement l’authentification d’un utilisateur, et va appliquer une stratégie de sécurité par défaut quelque soit l’action demandée.

Toujours dans le même fichier src/Application.php, ajoutez d’abord les imports nécessaires.

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;

Ensuite, ajoutez l’implémentation de l’interface:

class Application extends BaseApplication implements AuthenticationServiceProviderInterface

Puis modifiez la méthode middleware() et spécifiez la classe qui implémentera le middleware d’authentification. Dans ce cas, il s’agit de l’instance actuelle.

$middlewareQueue->add(new AuthenticationMiddleware($this));

Faites particulièrement attention à l’ordre : il doit être placé après BodyParserMiddleware et RoutingMiddleware.

Implémentation

Il faut ensuite ajouter la méthode getAuthenticationService(). Elle sera appelée à chaque fois qu’une requête est traitée.

public function getAuthenticationService(
    ServerRequestInterface $request
   ): AuthenticationServiceInterface
{
	$service = new AuthenticationService();

	// Automatic redirection when not authenticated
	$service->setConfig([
		'unauthenticatedRedirect' => Router::url([
			'prefix' => false,
			'plugin' => null,
			'controller' => 'Utilisateurs',
			'action' => 'login',
		]),
		'queryParam' => 'redirect',
	]);

	$fields = [
		IdentifierInterface::CREDENTIAL_USERNAME => 'nom',
		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' => 'Utilisateurs',
			'action' => 'login',
		]),
	]);
	
	$service->loadIdentifier('Authentication.Password', [
	  'resolver' => [
		'className' => 'Authentication.Orm',
		'userModel' => 'Utilisateurs',
	  ],
	  'fields' => [
		'username' => 'nom',
		'password' => 'password',
	  ],
	]);

	return $service;
}

Ce qu’on fait :

  • On définit avec setConfig() la stratégie à appliquer quand un utilisateur n’est pas connecté.
  • On spécifie les paramètres utilisés pour le formulaire d’identification.
  • On spécifie la liste des champs qui servent à identifier l’utilisateur.

Ici des modifications ont été apportées par rapport à l’exemple par défaut.

  • La classe du contrôleur qui gérera l’identification s’appelle UtilisateursController. Il faut donc spécifier « Utilisateurs » comme nom de contrôleur. CakePHP résoudra le nom complet lui-même.
  • Les champs de la table que l’on utilise sont le nom et password.
  • Notre table n’est pas appelée users mais utilisateurs. On le spécifie dans la clé resolver.

Charger le composant

Dans le contrôleur par défaut AppController.php (dont vos contrôleurs devront hériter), ajoutez l’appel suivant dans la méthode initialize().

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

L’erreur fréquente à ne pas reproduire: si vous écrivez une méthode initialize() dans un contrôleur enfant, n’oubliez pas d’appeler la méthode parent.

Désactiver l’authentification au cas par cas

Par défaut, toutes les actions nécessiteront une authentification. Mais vous pouvez modifier ce comportement en ajoutant l’événement beforeFilter() à chaque contrôleur où vous souhaitez que cela s’applique.

public function beforeFilter(EventInterface $event)
{
    $this->Authentication->allowUnauthenticated([
        'register', 'login', 'logout'
    ]);
}

On appelle la méthode allowUnauthenticated() de l’objet d’authentification, et on lui spécifie un tableau d’actions. Dans notre cas on autorise notamment login puisque c’est la méthode qui permet de gérer la requête d’auhentification.

Traiter la requête d’identification

Lorsqu’on valide le formulaire d’identification, voici l’action exécutée. La méthode est ajoutée au sein du contrôleur UtilisateursController.

public function login()
{
	if ($this->request->is('post')) 
	{
		$user = $this->Authentication->getResult();
		if ($user->isValid()) 
		{
			$target = $this->Authentication
				->getLoginRedirect() ?? '/';
			return $this->redirect($target);
		}
		
		$this->Flash->error(__("Bad user or password"));
	}
}

La méthode isValid() confirme si l’authentification s’est bien déroulée. Si c’est le cas on redirige l’utilisateur vers l’URL qu’on récupère à l’aide de getLoginRedirect(), et si celle-ci n’existe pas on redirigera vers la route « / ».

Dans le template associé, voici le code qui génère le formulaire:

<div class="users form">
<?= $this->Form->create() ?>
    <fieldset class="subfield">
        <legend><?= __("Saisissez vos identifiants") ?></legend>
        <?= $this->Form->control('nom',
                    ['label'=>'Identifiant']) ?>
        <?= $this->Form->control('password',
                    ['label'=>'Mot de passe']) ?>
  
<?= $this->Form->button(__('Connexion')); ?>
  </fieldset>
<?= $this->Form->end() ?>
</div>

Attention à bien respecter le nom des champs définis dans la configuration du middleware. Ils correspondent également aux champs définis dans le modèle.

Traiter la requête de déconnexion

Toujours dans le même contrôleur, on peut ajouter une action logout().

public function logout()
{
	$this->Authentication->logout();
	return $this->redirect([
		'controller' => 'Utilisateurs', 'action' => 'login'
	]);
}

La session de l’utilisateur est invalidée automatiquement.

Sources