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