lundi 16 février 2009

Authentification avec Zend_Auth et un formulaire ExtJS en MVC

Objectif :

Notre objectif dans ce tutoriel est de réaliser une authentification avec Zend_Auth et un formulaire ExtJS qui ressemblera  à ça:   

login

Pré-requis

Des connaissances en PHP, Zend Framework en particulier Zend_Auth et le model MVC sont fortement recommandé, pour avoir un aperçu je vous invite à suivre ces tutoriels.

dans ce tutoriel nous allons reprendre l’exemple Débutez avec Zend_Auth  qui explique très clairement comment fonctionne le processus d’authentification avec Zend_Auth et nous apporterons les modifications nécessaire pour qu’il puisse interagir avec notre formulaire AJAX.

Mise en œuvre

Pour arriver a notre but, nous aurons besoin de modifier le contrôleur et la vue d’authentification.

Le formulaire

Pour réaliser notre formulaire de login nous aurons besoin du Framework Ajax ExtJs que vous pouvez télécharger librement sur le  site officiel qui inclus une licence  open source selon l’usage

Lien de téléchargement ExtJS.

Une fois que vous avez téléchargé le Framework vous allons mettre celui-ci dans  dans le dossier prévu pour les scripts et nous allons créer un nouveau fichier login.js, à la fin votre dossier public aura la structure suivante:  

public

les fichiers styles.css et icon_padlock.png sont la juste pour définir l’icone (le petit cadenas) de la barre de titre de notre fenêtre.

icon_padlock icon_padlock.png

 

Public\css\styles.css
.image_login
{
padding-left:20px;
background:url(../images/icon_padlock.png) no-repeat 1px 0px;
}
.lbError
{
 color :red;
}

code css à rajouter a votre stylesheet.

 

public\js\login.js
Ext.onReady( function() {
 Ext.QuickTips.init();

 // Créer une variable qui va contenir notre FormPanel

  var login = new Ext.FormPanel( {
   labelWidth :80,
   url :'auth/login',
   frame :true,
   bodyStyle :'padding:5px;',
   defaultType :'textfield',
   monitorValid :true,
   // Création des deux champs nom d'utilisateur et mot de passe
   // la proprité name est celle qui est envoyez au serveur avec
   // la méthode POST.
   items : [ {
    fieldLabel :'Utilisateur',
    id :'username',
    name :'username',
    allowBlank :false
   }, {
    fieldLabel :'Mot de passe',
    id :'password',
    name :'password',
    inputType :'password',
    allowBlank :false
   }, {
    id :'lbError',
    name :'lbError',
    cls :'lbError',
    xtype :'label',
    text :'',
    width :160
   } ],

   buttons : [ {
    text :'Login',
    formBind :true,
    // Function lorsque en clique sur le boutton login
    handler : function() {

     login.getForm().submit(
       {
        method :'POST',
        waitTitle :'Conexion en cours...',
        waitMsg :'envoi des données...',
        
        success : function(form, action) {
         if (action && action.result) {
          var redirect = action.result.link;
          window.location = redirect;
         }
        },

        // en cas d'echec de l'autentification .
        // en renvois à l'utilisateur le motif

        failure : function(form, action) {

         login.getComponent('lbError').setText(
           action.result.msg.text);

         if (action.result.msg.code == '2') {
          login.getComponent('username').focus();
         } else if (action.result.msg.code == '3') {
          login.getComponent('password').focus();
         }

        }
       });
    }
   } ]
  });

  // ici on vas créer une fenetre qui va contenir Notre formPanel

  var win = new Ext.Window( {
   layout :'fit',
   title :'Identifiez vous',
   iconCls :'image_login',
   width :300,
   height :150,
   closable :false,
   resizable :false,
   modal :true,
   plain :true,
   border :false,
   items : [ login ]
  });
  win.show();
 });

 

Le but de ce tutoriel n’est pas de vous montrer comment créer une fiche avec ExtJS mais  de voir comment cette fiche va interagir avec l’action d’authentification, si vous voulez avoir plus de détails sur le code ci-dessus je vous recommande de jeter un œil à cet article et ce tutoriel (en anglais).

Néanmoins je tiens a vous expliquer ce qui ce passe lorsque on clique sur le bouton login.

Notre formulaire envoie une requête http  avec la méthode POST à l’url indiqué dans la première ligne mise en surbrillance (ligne 8) et attend au retour une réponse au format JSON sucess :true  ou sucess:false, nous incluons aussi d’autres informations tel que le lien de redirection en cas de succès  ou le text et le code de l’erreur dans la cas contraire.

affichage du formulaire

Pour pouvoir afficher notre formulaire nous devons initialiser le header de notre layout car vous aurais besoin peut être d’utiliser d’autres Widget ExtJs dans votre application.

Application\layouts\layout.phtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <title><?php echo $this->escape($this->title); ?></title>
    <link rel="stylesheet" type="text/css" media="screen" href="<?php echo $this->baseUrl();?>/public/css/styles.css" />
    <link rel="stylesheet" type="text/css" href="<?php echo $this->baseUrl();?>/public/js/ext/resources/css/ext-all.css"> 
 <script type="text/javascript" src="<?php echo $this->baseUrl();?>/public/js/ext/adapter/ext/ext-base.js"></script>
 <script type="text/javascript" src="<?php echo $this->baseUrl();?>/public/js/ext/ext-all.js"></script>
</head>
<body>
<div id="content">
    <h1><?php echo $this->escape($this->title); ?></h1>
 
    <?php echo $this->layout()->content; ?>
</div>
</body>

ensuite dans l’action index de notre contrôleur auth nous affichons le formulaire

Application\views\scripts\auth\index.phtml
<script type="text/javascript" src="<?php echo $this->baseUrl();?>/Public/Js/login.js"></script>

Vous pouvez maintenant essayez d’afficher votre formulaire, si ce n’est pas le cas vous avez certainement loupé quelque chose.

Une fois que notre formulaire est terminé nous allons  nous intéressés au contrôleur qui va faire le plus gros du travail.

Application\controllers\AuthController.phtml
<?php
class AuthController extends Zend_Controller_Action {
 
 public function indexAction() {
  $this->view->headTitle ( 'Login' );
 }
 
 public function loginAction() {
  
  $this->_helper->layout->disableLayout ();
         $this->_helper->removeHelper('viewRenderer');
  
  if ($this->_request->isPost ()) {
   
   Zend_Loader::loadClass ( 'Zend_Filter_StripTags' );
   $f = new Zend_Filter_StripTags ( );
   $username = $f->filter ( $this->_request->getPost ( 'username' ) );
   $password = $f->filter ( $this->_request->getPost ( 'password' ) );
   
   if (empty ( $username )) {
    $message = "{success: false, errors: { reason: 'Please provide a username.' }}";
   
   } else {
    // setup Zend_Auth adapter for a database table
    Zend_Loader::loadClass ( 'Zend_Auth_Adapter_DbTable' );
    $dbAdapter = Zend_Registry::get ( 'dbAdapter' );
    $authAdapter = new Zend_Auth_Adapter_DbTable ( $dbAdapter );
    $authAdapter->setTableName ( 'users' );
    $authAdapter->setIdentityColumn ( 'username' );
    $authAdapter->setCredentialColumn ( 'password' );
    
    // Set the input credential values to authenticate against
    $authAdapter->setIdentity ( $username );
    $authAdapter->setCredential ( md5 ( $password ) );
    
    // do the authentication
    $auth = Zend_Auth::getInstance ();
    
    try {
     $result = $auth->authenticate ( $authAdapter );
     
     switch ($result->getCode ()) {
      case Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND :
       $message = "{success: false, msg: {text: 'Utilisateur non trouvé !', code: '2'}}";
       break;
      case Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID :
       $message = "{success: false, msg: {text: 'mot de passe incorect', code: '3'}}";
       break;
      case Zend_Auth_Result::SUCCESS :
       
       $data = $authAdapter->getResultRowObject ( null, 'password' );
       
       if ($data->UserLevel > 0) {
        $auth->getStorage ()->write ( $data );
        $message = "{success:true, link: '" . $this->_request->getBaseUrl () . "'}";
       
       } else {
        $result->auth->clearIdentity ();
        
        $message = "{success:false, msg: {text: 'non autoriser, code:'2'}}";
       }
       break;
     }
    
    } catch ( Zend_Db_Adapter_Exception $e ) {
     
     $message = "{success:false, msg: {text: 'Erreur de conexion, " . $e->getMessage () . "', code:'6'}}";
    } catch ( Zend_Exception $e ) {
     
     $message = "{success:false, msg: {text: 'Erreur Systeme, " . $e->getMessage () . "', code:'6'}}";
    }
    
    $this->getResponse ()->clearBody ();
    $this->getResponse ()->setHeader ( 'Content-Type', 'text/x-json' );
    $this->getResponse ()->setBody ( $message );
   
   }
  }
 
 }

}

Si nous regardons le code de près nous remarquerons  qu'il n'ya pas de grand changement par apport au contrôleur définit dans le tutoriel que je vous est cité au début de cet article.

 

Application\controllers\AuthController.phtml
$this->_helper->layout->disableLayout ();
$this->_helper->removeHelper('viewRenderer');

Comme nous l’avons dit précédemment la réponse attendu par notre formulaire doit être au format JSON et ne doit contenir aucun autre caractère, c’est à cet effet que nous devons désactiver le layout de l’action login et le rendu de la form pour évité d’inclure notre entête dans la réponse.

 

Application\controllers\AuthController.phtml
    switch ($result->getCode ()) {
      case Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND :
       $message = "{success: false, msg: {text: 'Utilisateur non trouvé !', code: '2'}}";
       break;
      case Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID :
       $message = "{success: false, msg: {text: 'mot de passe incorect', code: '3'}}";
       break;
      case Zend_Auth_Result::SUCCESS :
       
       $data = $authAdapter->getResultRowObject ( null, 'password' );
       
       if ($data->UserLevel > 0) {
        $auth->getStorage ()->write ( $data );
        $message = "{success:true, link: '" . $this->_request->getBaseUrl () . "'}";
       
       } else {
        $result->auth->clearIdentity ();
        
        $message = "{success:false, msg: {text: 'non autoriser, code:'2'}}";
       }
       break;
     }
    
    } catch ( Zend_Db_Adapter_Exception $e ) {
     
     $message = "{success:false, msg: {text: 'Erreur de conexion, " . $e->getMessage () . "', code:'6'}}";
    } catch ( Zend_Exception $e ) {
     
     $message = "{success:false, msg: {text: 'Erreur Systeme, " . $e->getMessage () . "', code:'6'}}";
    }

Dans cette partie nous créons notre chaine JSON selon la réponse de Zend_Auth.

la réponse prend le format {sucess:true, link:’url de redirection’} en cas de succès et {sucess:false,msg{text:’message d’erreur,code:’code erreur’}} en cas d’échec

 

Application\controllers\AuthController.phtml
$this->getResponse ()->clearBody ();
$this->getResponse ()->setHeader ( 'Content-Type', 'text/x-json' );
$this->getResponse ()->setBody ( $message );

et finalement nous envoyons la réponse à notre formulaire qui va nous rediriger vers l’url que nous lui avons indiqué ou nous affiche un message d’erreur et nous invite a s’authentifier à nouveau.

Vous pouvez télécharger le code source complet. de l’exemple.

11 commentaires:

  1. Merci bien pour ce tutoriel mais il ne marche pas (il reste dans l'état "connexion en cours")malgré que j'ai modifié le fichier .ini avec mes propres données et j'ai fait tous les étapes. y 'a t-il un problème ou une faute quelques part, merci de vérifier et nous donner la solution.

    RépondreSupprimer
  2. salut, y 'a t-il quelqu'un pour repondre a ma question??!!

    RépondreSupprimer
  3. désolé je pour ce retard, vous pouvez télécharger le source complet il est fonctionnel, j'ai testé avant de poster.
    essai de déboguer avec la console firebug, pour voir votre requete et ça réponse du serveur

    RépondreSupprimer
  4. Tuto plutot cool un peu de debug mais rien de méchant.
    Merci beaucoup pour le partage.

    RépondreSupprimer
  5. En ce qui me concerne cela ne marche pas, il me met "mot de passe incorrect" alors que je saisi le bon mot de passe dans la bdd

    une idée ?

    RépondreSupprimer
  6. comment est stocké ton mot de passe ? car lors de l'authentification nous attendons un mot haché en MD5

    si ton mot de passe est stocké en claire il faudrait remplacer

    $authAdapter->setCredential ( md5 ( $password ) );
    par
    $authAdapter->setCredential ($password);

    RépondreSupprimer
  7. Merci pour ce tuto. Par contre quand je clique sur Login, j'ai l'erreur suivante sous Firebug : "missing ) in parenthetical" dans ext-all.js ligne 8, et la page reste dans l'état "Connexion en cours".
    Avez-vous déjà rencontré ce problème ? Si oui merci de m'en faire-part afin de trouver une solution.

    RépondreSupprimer
  8. Merci pour ce tuto, mais je rencontre un petit soucis. Il me semble que dans ce cas de figure le mot de passe n'est pas crypté par le client mais par le serveur ce qui signifie qu'il passe en clair sur le réseau. N'est-il pas possible de le crypter via le fichier login.js ?

    RépondreSupprimer
  9. Nice tuto, thanks for sharing

    RépondreSupprimer
  10. Bonjour,

    Impossible de faire fonctionner ce tuto,
    je suis en ZF 1.11.10

    Si quelqu'un a une solution.

    RépondreSupprimer
  11. Bonjour

    j'ai implemente votre application login sous extjs 4 et zend framework zf 1.11.11
    j'ai l'erreur sous firebug
    action.result is undefined
    [Break On This Error]

    login.getComponent('lbError').setText(action.result.msg.text);
    merci de votre aide. s'est tres urgent merci de vos retours

    RépondreSupprimer