Tuesday, May 3, 2011

ExtJS / PHP - Login form and user authentication

Something that's needed time and time again is a login page that provides user authentication. I got the original PHP code for this from marakana.com. I created a front end using ExtJS and modified the code to store an md5 hash of the password in a flat file. As some point I'd like to add an install script to create a SQL database and store the password there instead.

This is meant for smaller sites / applications where SSL is deemed to complex or expensive.

Security is provided using a 'Challenge-Response Authentication Method'. Sending a password using clear text provides an easy opportunity for it to be sniffed by an attacker. The next step is to encrypt the password but this would could still allow access through a reply attack. In the code below, the server generates a random string which it then sends to the client. The client then combines this with the password and returns it to the server for authentication.

I've included sample code in yourpage.php which allows the user to change their user name and password. The changes are carried out on ther server-side by settings.php.

As well as the files below, you will also need Paj's MD5 algorithm available here;

http://pajhome.org.uk/crypt/md5/


authenticate.php
<?php 
  function getPasswordForUser($username) {
    $config_file = file_get_contents("config.json");
    $config_array = json_decode($config_file, true);
    return $config_array["config"][0]["password"];
  }
  function validate($challenge, $response, $password) {
    return md5($challenge . $password) == $response;
  }
  function authenticate() {
    if (isset($_SESSION[challenge]) && isset($_REQUEST[username]) && isset($_REQUEST[response])) {
      $password = getPasswordForUser($_REQUEST[username]);
      if (validate($_SESSION[challenge], $_REQUEST[response], $password)) {
        $_SESSION[authenticated] = "yes";
        $_SESSION[username] = $_REQUEST[username];;
        unset($_SESSION[challenge]);
      } else {
        echo '{"success":false,"message":"Incorrect user name or password"}';
        exit;
      }
    } else {
      echo '{"success":false,"message":"Session expired"}';
      exit;
    }
  }
  session_start();
  authenticate();
  echo '{"success":true}';
  exit();
?>


common.php
<?php
  session_start();
  function is_authenticated() {
    return isset($_SESSION[authenticated]) && $_SESSION[authenticated] == "yes";
  }
  function require_authentication() {
    if (!is_authenticated()) {
      header("Location:index.php");
      exit;
    }
  }
?>


index.php
<?php
  session_start();
  session_unset();
  srand();
  $challenge = "";
  for ($i = 0; $i < 80; $i++) {
    $challenge .= dechex(rand(0, 15));
  }
  $_SESSION[challenge] = $challenge;
?>
<html>
  <head>
     <title>Login</title>
     <link rel="stylesheet" type="text/css" href="
http://extjs.cachefly.net/ext-3.3.0/resources/css/ext-all.css" />
     <!-- Calls to ExtJS library files from Cachefly. -->
     <script type="text/javascript" src="
http://extjs.cachefly.net/ext-3.3.0/adapter/ext/ext-base.js"></script>
     <script type="text/javascript" src="
http://extjs.cachefly.net/ext-3.3.0/ext-all-debug.js"></script>
     <script type="text/javascript" src="md5.js"></script>
     <script type="text/javascript">
       Ext.BLANK_IMAGE_URL = 'images/s.gif';
       Ext.onReady(function(){
         var loginForm = new Ext.form.FormPanel({
           frame: true,
           border: false,
           labelWidth: 75,
           items: [{
             xtype: 'textfield',
             width: 190,
             id: 'username',
             fieldLabel: 'User name'
           },{
             xtype: 'textfield',
             width: 190,
             id: 'password',
             fieldLabel: 'Password',
             inputType: 'password',
             submitValue: false
           },{
             xtype: 'hidden',
             id: 'challenge',
             value: "<?php echo $challenge; ?>",
             submitValue: false
           }],
           buttons: [{
             text: 'Login',
             handler: function(){
               if(Ext.getCmp('username').getValue() !== '' && Ext.getCmp('password').getValue() !== ''){
                 loginForm.getForm().submit({
                   url: 'authenticate.php',
                   method: 'POST',
                   params: {
                     response: hex_md5(Ext.getCmp('challenge').getValue()+hex_md5(Ext.getCmp('password').getValue()))
                   },
                   success: function(){
                     window.location = 'yourpage.php';
                   },
                   failure: function(form, action){
                     Ext.MessageBox.show({
                       title: 'Error',
                       msg: action.result.message,
                       buttons: Ext.Msg.OK,
                       icon: Ext.MessageBox.ERROR
                     });
                   }
                 });
               }else{
                 Ext.MessageBox.show({
                   title: 'Error',
                   msg: 'Please enter user name and password',
                   buttons: Ext.Msg.OK,
                   icon: Ext.MessageBox.ERROR
                 });
               }
             }
           }]
         });
         var loginWindow = new Ext.Window({
           title: 'Login',
           layout: 'fit',
           closable: false,
           resizable: false,
           draggable: false,
           border: false,
           height: 125,
           width: 300,
           items: [loginForm]
         });
         loginWindow.show();
       });
     </script>
   </head>
   <body>
   </body>
</html>


config.json
{
  "config":[{
    "username":"admin",
    "password":"21232f297a57a5a743894a0e4a801fc3"
  }]
}


yourpage.php
<?php
  require("common.php");
  require_authentication();
?>

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Your Page</title>

    <link rel="shortcut icon" href="favicon.ico" />
    <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-3.3.0/resources/css/ext-all.css" />
    <link rel="stylesheet" type="text/css" href="editor.css" />

    <!-- Calls to ExtJS library files from Cachefly. -->
    <script type="text/javascript" src="http://extjs.cachefly.net/ext-3.3.0/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="http://extjs.cachefly.net/ext-3.3.0/ext-all.js"></script>
    <script type="text/javascript" src="md5.js"></script>
    <script type="text/javascript" src="editor.js"></script>

    <script type="text/javascript">
      function changeSettings(){
        var settingsDialog = new Ext.Window({
          title: 'Settings',
          id: 'settingsdialog',
          border: false,
          height: 140,
          layout: 'fit',
          resizable: false,
          width: 300,
          items: [{
            xtype: 'form',
            frame: true,
            border: false,
            labelWidth: 75,
            items: [{
              xtype: 'textfield',
              id: 'input_username',
              fieldLabel: 'User name',
              allowBlank: false,
              width: 190
            },{
              xtype: 'textfield',
              id: 'input_password',
              fieldLabel: 'Password',
              inputType: 'password',
              allowBlank: false,
              width: 190
            }]
          }],
            buttons: [{
              text: 'Submit',
              id: 'save_config',
              handler: function(){
                if(Ext.getCmp('input_username').isValid() && Ext.getCmp('input_password').isValid()){
                  Ext.Ajax.request({
                    url: 'settings.php',
                    params: {
                      username: Ext.getCmp('input_username').getValue(),
                      password: hex_md5(Ext.getCmp('input_password').getValue())
                    },
                    success: function(response, opts) {
                      var obj = Ext.decode(response.responseText);
                      if (obj.success) {
                        Ext.MessageBox.show({
                          title: 'Your Page',
                          msg: 'Your changes have been saved',
                          buttons: Ext.MessageBox.OK,
                          icon: Ext.MessageBox.INFO
                        });
                      }else{
                        Ext.MessageBox.show({
                          title: 'Your Page',
                          msg: 'Unable to save changes',
                          buttons: Ext.MessageBox.OK,
                          icon: Ext.MessageBox.ERROR
                        });
                      }
                    },
                    failure: function(response, opts) {

                    }
                  });
                } else {
                  Ext.MessageBox.show({
                    title: 'Your Page',
                    msg: 'Please enter user name and password',
                    buttons: Ext.MessageBox.OK,
                    icon: Ext.MessageBox.ERROR
                  });
                }
              }
            },{
              text: 'Close',
              handler: function(){
                settingsDialog.close();
              }
            }]
        });

        settingsDialog.show();
      }
      Ext.onReady(function(){
        Ext.QuickTips.init();
        var contentPanel = new Ext.Panel({
          frame: true,
          layout: 'fit',
          items: [{
            xtype: 'textarea'
          }],
          tbar: [{
            xtype: 'button',
            text: 'Settings',
            tooltip: 'Change Settings',
            handler: function(){
              changeSettings();
            }
          },{
            xtype: 'button',
            text: 'Logout',
            tooltip: 'Logout',
            handler: function(){
              window.location = 'index.php';
            }
          }]
        });

        var viewport = new Ext.Viewport({
          layout: 'fit',
          items: [contentPanel]
        });

      });
    </script>
  </head>
  <body>
  </body>
</html>


settings.php
<?php
  $config_json = "{
  \"config\":[{
    \"username\":\"" .$_POST['username']. "\",
    \"password\":\"" .$_POST['password']. "\"
  }]
}";

  file_put_contents('config.json', $config_json);
  echo "{\"success\":true}";

?>


Note: User name and password both set to 'admin' in the above example.

At some point in the future I aim to extend this to allow multiple users.

A couple of screen shots;

Login window


User not authenticated


10 comments:

  1. Corrected a typo in the calls to the ExtJS libraries in index.php and added a note giving the user name and password as set in the config.json file above.

    Really should test code before I post it! ;o)

    ReplyDelete
  2. Bit of a slow day today so added some code into yourpage.php to allow changing of user name and password.

    ReplyDelete
  3. Thank you for this Boran. I tried to execute the code by copying and pasting. by when i execute it i get this error :
    Notice: Use of undefined constant challenge - assumed 'challenge' in C:\EasyPHP5210\www\cbs\index.php on line 16. Which corespond to this in index page : $_SESSION[challenge] = $challenge;
    how cant i resolve it?

    ReplyDelete
  4. I think this is due to newer versions of PHP requiring single-quote marks around the session/post variable names.

    Try changing to - $_SESSION['challenge']

    You will need to do this on all occurences of $_POST and $_SESSION. I'll edit the code above when I get a chance.

    ReplyDelete
  5. ReferenceError: hex_md5 is not defined
    [Break On This Error]

    ...: hex_md5(Ext.getCmp('challenge').getValue()+hex_md5(Ext.getCmp('password').getV...

    When login got the above error.
    If this tutorial with mySQL then share with me

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Hi, I am not able to login with username=admin and password=whatever you have specified in the example. It showing incorrect username and password.

    ReplyDelete
  8. Hi,

    I cant find your file editor.css and also the editor.js.. can you help me for this? thanks!

    ReplyDelete