Interface d’administration d’un site CakePHP avec le Composant Auth
Nous souhaitons fournir à notre client un panneau d’administration pour qu’il puisse éditer lui-même le contenu de son site, par exemple un simple blog avec des Articles. Nous nous imposons plusieurs contraintes :
- L’administration doit être accessible à l’adresse
/admin; - Toutes les actions de cet espace d’administration doivent avoir une url commençant par
/admin; - L’administration doit avoir un layout différent de celui du site public ;
- L’accès à l’administration requiert l’authentification d’un administrateur autorisé par un couple login / mot de passe. Nous nous limiterons ici à un seul type (ou rôle) d’utilisateur.
1. Mise en place de l’administration
1.1. Prise en compte des url commençant par /admin
Nous allons indiquer à CakePHP que nous souhaitons préfixer toutes les URL des actions de l’administration par /admin. Cela se fait très simplement en décommentant la ligne 65 du fichier {app}/config/core.php :
64 65 | // {app}/config/core.php Configure::write('Routing.admin', 'admin'); |
A présent, si nous appelons l’adresse /admin/articles/add dans notre navigateur, cela renvoie sur l’action admin_add que nous définirons dans le Contrôleur ArticlesController.
1.2. Page d’accueil de l’administration
La page d’accueil de notre administration affiche simplement un lien vers la création d’un nouvel article, suivi de la liste des 10 articles les plus récemment publiés. Nous allons donc créer l’action admin_index dans le Contrôleur des Articles :
1 2 3 4 5 6 | // {app}/controllers/articles_controller.php function admin_index() { $articlesRecents = $this->Article->findAll(null, null, 'created DESC', 10); $this->set('articlesRecents', $articlesRecents); } |
Nous créons la vue correspondante :
1 2 3 4 5 6 7 8 9 10 11 12 13 | // {app}/views/articles/admin_index.ctp <h2>Les derniers articles</h2> <a href="/admin/articles/add">Créer un article</a><br/> <?php foreach($articlesRecents as $article) { e($html->link( $article['Article']['titre'], '/admin/articles/edit/'.$article['Article']['id']) ); e('<br/>'); } ?> |
Nous pouvons d’ores et déjà nous rendre à l’adresse /admin/articles/index. Nous remarquons deux problèmes : d’une part nous voulons accéder à l’administration via l’URL /admin et non pas /admin/articles/index, et d’autre part la vue que nous venons de créer s’affiche avec le même layout que le site public, alors qu’il est préférable de fournir une mise en page plus légère et plus fonctionnelle pour les actions d’administration.
1.3. Une route pour le point d’entrée dans l’administration
Nous allons créer une nouvelle route pour relier l’adresse /admin à la page d’accueil de notre administration, soit /admin/articles/index.
1 2 3 4 5 6 7 8 9 | // {app}/config/routes.php Router::connect( '/admin', array( 'controller' => 'articles', 'action' => 'index', 'prefix' => 'admin' ) ); |
Notez le paramètre ‘prefix’.
1.4. Un layout réservé à l’administration
Nous allons maintenant faire en sorte que toutes les pages de l’administration aient un layout différent de celui du site public. Le meilleur endroit pour décider de cela : la méthode beforeRender du Contrôleur général de l’application :
// {app}/app_controller.php function beforeRender() { if(isset($this->params['prefix']) && $this->params['prefix'] == 'admin') { $this->layout = 'admin_default'; } }
Et nous créons un layout réservé à l’administration à cet emplacement : {app}/views/layouts/admin_default.ctp.
// {app}/views/layouts/admin_default.ctp <?php e($html->docType('html4-strict')); ?> <html> <head> <?php e($html->charset('iso-8859-1')); ?> </head> <body> <h1>Administration</h1> <div id="main"> <?php // Message d'erreur éventuel envoyé par Auth $session->flash('auth'); // Autre message éventuel $session->flash(); // Contenu e($content_for_layout); ?> </div> </body> </html>
Cette fois, si nous nous rendons à l’adresse /admin, nous arrivons bien sur la page d’accueil de l’administration, et cette page dispose d’un layout différent que celui du site public. Nous pouvons mettre en place l’authentification des utilisateurs autorisés avec le Composant Auth.
2. L’authentification
2.1. Gestion des comptes utilisateur
Nous commençons par créer la table qui va contenir les comptes utilisateur autorisés à accéder à l’administration. Nous allons en créer un par défaut, sinon nous ne pourrons jamais nous connecter à l’administration.
CREATE TABLE `users` ( `id` smallint(5) UNSIGNED NOT NULL AUTO_INCREMENT, `login` varchar(16) NOT NULL, `password` char(40) NOT NULL, `disabled` tinyint(1) UNSIGNED NOT NULL, `created` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM;
Les champs de la table sont les suivants :
id: clé primaire ;login: identifiant de l’administrateur, pouvant comporter jusqu’à 16 caractères ;password: résultat du passage de la clé de sécurité suivie du mot de passe de l’administrateur par une méthode de hachage (SHA1 par défaut, qui produit toujours une chaine de 40 caractères) ;disabled: indique si l’utilisateur est bloqué (1) ou non (0) ;created: date de création du compte. Géré par CakePHP.
INSERT INTO `users` (`id`, `login`, `password`, `disabled`, `created`) VALUES (1, 'admin', SHA1(6a10cdde80fb56150efda09365f91579ea74a944admin), 0, NOW());
Nous insérons un administrateur dont le login est admin et le mot de passe admin. Remarquez l’utilisation de la fonction SHA1 disponible directement dans MySQL, à laquelle nous passons une chaine de caractère composée de la clé de sécurité (que l’on peut trouver dans le fichier {app}/config/core.php aux alentours de la ligne 150) et du mot de passe de l’administrateur. Tout ce processus sera ensuite entièrement géré par CakePHP, mais il nous faut bien un premier compte pour se connecter à l’administration.
Maintenant que la table users est créée, nous avons besoin du Modèle User et du Contrôleur UsersController :
1 2 3 4 5 | // {app}/models/user.php class User extends AppModel { var $name = 'User'; } |
1 2 3 4 5 | // {app}/controllers/users_controller.php class UsersController extends AppController { var $name = 'Users'; } |
2.2. Initialisation du Composant Auth
CakePHP fournit un Composant dédié au processus d’authentification utilisateur : Auth. Ce Composant se charge d’autoriser ou de refuser l’accès à certaines actions en fonction de critères que nous allons lui indiquer, et de rediriger un utilisateur non autorisé sur un formulaire de connexion. Auth se charge également de gérer la fonction de connexion et de déconnexion à l’administration.
Dans la mesure où l’administration concerne des actions dans chaque Contrôleur de notre application, nous allons gérer les autorisations dans le Contrôleur général. Comme la restriction d’accès doit se faire avant tout autre traitement, nous plaçons notre logique dans la méthode beforeFilter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | // {app}/app_controller.php class AppController extends Controller { var $helpers = array ('Html', 'Text', 'Form'); var $components = array('Auth'); function beforeFilter() { if(isset($this->Auth)) { $this->Auth->userModel = 'User'; $this->Auth->fields = array('username' => 'login', 'password' => 'password'); $this->Auth->userScope = array('User.disabled' => 0); $this->Auth->loginAction = '/users/login'; $this->Auth->loginRedirect = '/admin/articles'; $this->Auth->loginError = "Identifiant ou mot de passe incorrects."; $this->Auth->logoutRedirect = '/'; $this->Auth->authError = "Vous n'avez pas accès à cette page."; $this->Auth->autoRedirect = true; $this->Auth->authorize = 'controller'; if((empty($this->params['prefix']) || $this->params['prefix'] != 'admin') && $this->action != 'login') { $this->Auth->allow(); } } } function isAuthorized() { return true; } function beforeRender() { if(isset($this->params['prefix']) && $this->params['prefix'] == 'admin') { $this->layout = 'admin_default'; } } } |
Nous avons ici volontairement défini toutes les variables possibles du Composant Auth. Quelques explications :
$this->Auth->userModel = 'User';
Nom du Modèle qui gère les comptes utilisateur.$this->Auth->fields = array('username' => 'login', 'password' => 'password');
Nous indiquons au Composant les champs du Modèle qui doivent être vérifiés lors d’une identifiation, ici login et password.$this->Auth->userScope = array('User.disabled' => 0);
Nous pouvons ici définir une condition supplémentaire pour valider une identification : dans notre cas nous ne validons pas un utilisateur dont le compte est bloqué.$this->Auth->loginAction = '/users/login';ou$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
Nom du contrôleur et de l’action qui va afficher le formulaire de connexion.$this->Auth->loginRedirect = '/admin/articles/index';ou$this->Auth->loginRedirect = array('controller' => 'articles', 'action' => 'index', 'prefix' => 'admin');
Si l’utilisateur a essayé d’atteindre une page nécessitant une authentification, il est aussitôt redirigé vers le login mais l’adresse de la page refusée est gardée dans la session. Si l’utilisateur se connecte, il est renvoyé vers l’adresse connue dans la session, sinon il est renvoyé sur la page définie ici.$this->Auth->loginError = "Identifiant ou mot de passe incorrects.";
Message d’erreur affiché si le couple login / mot de passe saisi dans le formulaire de connexion n’est pas connu.$this->Auth->logoutRedirect = '/';
Page de destination en cas de déconnexion volontaire, dans notre cas la page d’accueil du site public.$this->Auth->authError = "Vous n'avez pas accès à cette page.";
Message d’erreur affiché en cas de tentative d’accès à une page qui nécessite une authentification.$this->Auth->autoRedirect = true;
Définit si Auth doit automatiquement rediriger sur $this->Auth->loginRedirect ou non en cas d’identification correcte. Cela peut être utile de mettre cette variable à false si nous avons un traitement à effectuer juste après la connexion (gestion d’un cookie ou autre). Ce traitement devra se faire dans l’action login du Contrôleur UsersController.$this->Auth->authorize = 'controller';
Lorsque cette variable vaut ‘controller’, Auth va chercher une méthodeisAuthorizeddans le AppController et/ou dans le Contrôleur en cours. Cette méthode pourra accueillir des traitements supplémentaire pour déterminer si l’utilisateur enregistré en session a bien le droit d’aller plus loin. Elle doit renvoyer true ou false.
En l’état, toutes les actions sont bloquées, sauf /users/login et /users/logout (merci à Avairet pour avoir soulevé le problème). Nous définissons donc les critères qui ne déclenchent pas une demande d’authentification.
1 2 3 4 | if((empty($this->params['prefix']) || $this->params['prefix'] != 'admin') && $this->action != 'login') { $this->Auth->allow(); } |
Si la variable $this->params['prefix'] n’existe pas ou qu’elle ne vaut pas ‘admin’, et que l’action demandée n’est pas le login, alors on autorise l’accès.
2.3. Les actions login et logout
Dans le Contrôleur UsersController :
// {app}/controllers/users_controller.php function login() { $this->layout = 'admin_default'; }
Nous définissons uniquement le layout, tout le traitement sera pris en charge par le Composant (vérification du couple login / mot de passe).
La vue correspondante :
1 2 3 4 5 6 7 8 | // {app}/views/users/login.ctp $this->pageTitle = "Identification requise"; // Formulaire de connexion e($form->create('User', array('action' => 'login'))); e($form->input('login', array('label' => 'Identifiant :'))); e($form->input('password', array('label' => 'Mot de passe :'))); e($form->end("Connexion")); |
Voyons l’action de déconnexion :
1 2 3 4 5 6 | // {app}/controllers/users_controller.php function logout() { $this->Session->setFlash("Vous êtes maintenant déconnecté."); $this->redirect($this->Auth->logout()); } |
Pierre-Emmanuel Fringant
Encore un article pertinent et clair !
Juste ce dont j’avais besoin ce jour… quelle coïncidence !
Bon, juste quelques questions quand même, parce que mes test n’ont pas été concluants :
1) le modèle utilisé doit-il forcément s’appeler “User” ?
2) peut-on avoir les “loginAction” et “loginRedirect” dans des contrôleurs différents ?
3) ton accueil principal de l’admin est en fait lié à un contrôleur métier “Articles”, mais j’ai besoin de faire un accueil Admin général, indépendant de toute action d’un contrôleur métier. Dans cet accueil général je souhaiterai avoir le formulaire de login si l’utilisateur n’est pas connecté, puis le menu de mon interface d’admin (avec cette fois des liens vers des actions admin de mes contrôleurs métiers) quand l’authentification est validée. est-ce possible ?
21 février 2008 à 16:19
Auteur : Avairet