Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / PHP

Login Project - Implement a Website Login mechanism

Rate me:
Please Sign up or sign in to vote.
5.00/5 (13 votes)
5 Jul 2021CPOL17 min read 15.6K   377   27   14
This article provides the code necessary to implement a website login mechanism without using third-party software.

1. Introduction Table of Contents

Over the past few years, I have been constructing and maintaining a website for a non-profit charitable organization. Until recently, the site contained non-proprietary information such as:

  • A home page with hours of operation, meeting days, directions, etc.
  • A menu allowing items such as the following to be accessed:
    • A carousel of upcoming events.
    • Rules of Games played in the organization's facility.
    • The organization's officers and the officers of subordinate organizations.
    • Calendars of the current and future months.
    • List of Sponsors.
  • The HTML pages that respond to menu selections.

All-in-all, it was a relatively simple site to construct and maintain.

However, members asked that minutes of meetings, profit and loss statements, organization documents (e.g., constitution, bylaws, etc.), drafts of documents, etc. be added. Members indicated that they wanted these items to be kept private and not be accessible to casual visitors to the site. The members' desires required a redesign of the website.

2. Requirements Table of Contents

There were a number of requirements that were to be levied against the new portion of the website.

  • No third party software, other than those offered by the hosting platform, was to be used. This effectively eliminated all forms of software that promised out-of-the-box login solutions (including Microsoft).
  • Implementation was limited to:
    • HTML
    • CSS
    • JavaScript
    • PHP - to interface between JavaScript and SQL
    • SQL - for database access only
    • PNG - for graphic objects

3. Overview Table of Contents

The website that originally had a simple directory structure now had to be partitioned into public and private portions. The public part was named "Public", the private part was named "Members", and that part that contained items common to both was named "Common". The final directory structure took on the following form:

directory structure

Note that directory names are capitalized and file names are lowercase (the host uses a variant of UNIX and thus the casing).

To enable a number of changes, the website landing page was converted to a redirection page that landed the visitor on the index.html in the Public directory. This was accomplished by the following:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Redirect</title>
    <meta http-equiv="refresh"
          content="0; URL=Public/index.html" />
  </head>
  <body>
  </body>
</html>

4. Redesign Table of Contents

The first task was to revise the website menu.

old menu

The only change needed was to add a "Members Only" item to the menu.

revised menu

When a visitor selects the Member Only item, he is directed to the member_login.html page.

5. HTML Table of Contents

This section describes member_login.html and its components.

5.1. member_login.html page Table of Contents

This page is made up of a main page landing <div> and five modal popup <div>s that perform most of the work. An overview of the member_login.html page follows.

overview

Within the HTML for that page is:

<link rel="stylesheet"
      href="../../Common/CSS/w3.css" />

<link rel="stylesheet"
      href="../../Common/CSS/member_login.css"
      type="text/css"
      media="screen" />
⋮
<script src="../../Common/Scripts/SHA256.js"></script>
<script src="../../Common/Scripts/cookies.js"></script>
<script src="../../Common/Scripts/mock.js"></script>
<script src="../../Common/Scripts/member_login.js"></script>
⋮
<script>
  window.onload =
    function ( )
      {
      MemberLogin.initialize_login ( );
      if ( MemberLogin.already_logged_in ( ) )
        {
        document.location = "../../Members/members_area.html";
        }
      };
</script>

There are two CSS entries:

  • member_login.css - developed during this project.
  • w3.css - comes from W3.CSS [^]. That CSS was used throughout the organization's website.

There are four external JavaScript files:

  • SHA256.js - used to hash passwords
  • cookies.js - utilities to manipulate cookies
  • mock.js - creates a mock database, see below
  • member_login.js - developed during this project to support the login process

When the member_login.html page first loads, the onload handler is invoked. That handler first invokes the initialize_login ( ) entry point of MemberLogin to initialize the global variables used by MemberLogin. Then the onload handler determines if the visitor has already logged in. At a successful login, a session cookie is created. If the cookie already exists, the visitor has already logged in and is now directed to members_area.html. If the cookie does not exist, the onload handler exits, thereby placing the visitor in the main_page <div>.

Because the cookie is a session cookie, if the visitor does log in but then exits the browser, the session cookie will be deleted by the browser. In that case, the visitor will be required to log on again.

5.1.1. main page <div> Table of Contents

main page <div> presents the visitor with:

login landing div

main_page <div> is not entered if the window.onload event handler determines that the visitor is already logged in. If main page <div> is entered, it provides the visitor with the ability to either login to the member area or register.

Within the click handlers for the Login and Register buttons in the main_page <div> appear

C#
<popup-name>.style.display='block'

This is the mechanism used to cause the various popups to appear. In the popups themselves, in the click handler for the Cancel button, appears

C#
<this-popup-name>.style.display='none'

This causes the current popup to close, returning the visitor to the popup from whence he came.

In main_page <div>, the visitor may choose to log in to the member area, register to be able to log in, or cancel further action.

Whenever a visitor clicks Cancel, on any page of the login process, the visitor is always returned to the main_page <div> or to the original referrer of the member_login.html page.

If the visitor clicks Register, the visitor is presented with the instruction_popup <div> of the member_login.html page; if the visitor clicks Login, the visitor is presented with the login_popup <div> of the member_login.html page.

5.1.2. instructions_popup <div> Table of Contents

The instructions_popup <div> is a cosmetic addition that instructs the visitor how to proceed through the Registration and Login Processes. (It is surprising how many visitors contact me for help without reading these instructions.)

instructions_popup <div> presents the visitor with:

instruction member login page

If the visitor clicks Continue, the visitor is directed to the member_id_popup <div> of the member_login.html page.

5.1.3. member_id_popup <div> Table of Contents

When the members only area was proposed, there was concern that casual visitors might be able to visit the organization's proprietary area. The solution was to require a login. However, just asking visitors for a username and password would not accomplish the desired effect.

The organization, at the national level, provides each member with a numbered membership card. Although the membership card is reissued annually, the member identification number is constant. Because usually only the member knows his member identification number, that number became the key to limiting usernames and passwords to members only. By a relatively simple process, the local organization's member identification numbers are downloaded from the national database and inserted into the database maintained on the organization's website.

It is that number that is required by the member_id_popup <div>.

The member_id_popup <div> presents the visitor with:

member id member login page

When the visitor enters a Member ID and clicks Verify, the Login Process looks up the supplied id in the database on the organization's website. There are three results to the look up: the id is recognized, the id is not recognized, or the id has already been used. In the latter two cases, the visitor is presented with one of the following:

verification failed member login page id in use member login page

If the verification of the id succeeds, the visitor is presented with the register_popup <div>.

5.1.4. register_popup <div> Table of Contents

The register_popup <div> presents the visitor with:

registration member login page

For both the User ID and the Password, the visitor may enter any sequence of alphanumeric characters ( 'a' through 'z'; 'A' through 'Z'; and '0' through '9'). The supplied character string must be between 6 and 64 characters in length and not include spaces or special characters.

The valication of the visitor's input is directed by the two Regex constants USER_ID_REGEX and PASSWORD_REGEX in member_login.js. By changing these constants, the input required for a username or password to be acceptable may be modified. (A note of caution: some characters may interfere with PHP or SQL execution.)

The registration process is accomplished by invoking the register ( ) entry point in MemberLogin. That method validates the data entered by the visitor and, if successful, adds the visitor's credentials to the organization's database. If registration is successful or the visitor clicked Login in the main page <div>, the visitor is directed to the login_popup <div> of the member_login.html page.

5.1.5. login_popup <div> Table of Contents

The login_popup <div> presents the visitor with:

login member login page

The Login Process is accomplished by invoking the login ( ) entry point in MemberLogin. That method validates the credentials entered by the visitor against the credentials in the organization's database. The visitor is given three attempts to successfully login. When he does, he is admitted to the members area.

For any number of reasons, a user may wish to change his password. If the user clicks Forgot password?, the user is directed to the reset_popup <div> of the member_login.html page.

5.1.6. reset_popup <div> Table of Contents

The reset_popup <div> presents the visitor with:

reset popup

Resetting the password is accomplished by invoking the reset_password ( ) entry point in MemberLogin. That method insures that both the user id and member id have been used.

6. Database Table of Contents

The database that contains member login data is maintained on the hosting service's database server with the name Members_DB. The database contains two tables and six procedures.

6.1. Tables Table of Contents

6.1.1. member_id Table of Contents

member id

The member_id table contains all member identification numbers that will be accepted during the registration process. The values in this table are derived from the national organization's data, maintained on their website.

Unfortunately, in my case, the national organization was unable (or unwilling) to create a web service [^] that would provide validation of member identification numbers. As a result, it is necessary to download a CSV [^] database, manipulate it, and upload the manipulated values (member ids) to the member_id table.

6.1.2. member_user_salt_password Table of Contents

member user salt password

When a visitor successfully registers, a record (row) in the member_user_salt_password table is created. User credentials are maintained in this table. Although the member_user_salt_password table is accessed through the user_id, the attribute member_id remains in the table for the purposes of resetting the password. The user wishing to change a password must know both the user_id and the member_id.

6.2. Procedures Table of Contents

Database procedures are coded in SQL. Each database procedure is accessed through a PHP function, itself invoked by a JavaScript method found in the module MemberLogin. The interactions between JavaScript, PHP, SQL, and the database can be found here.

6.2.1. delete_member Table of Contents

delete_member is referenced by MemberLogin.member_deleted to delete an existing member from Member_DB.member_user_salt_password table. MemberLogin.member_deleted returns true, if the deletion from Member_DB.member_user_salt_password is successful; otherwise, false.

PROCEDURE `delete_member` ( IN `mid` VARCHAR ( 16 ), 
                            IN `uid` VARCHAR ( 64 ))
    NO SQL
BEGIN
    DELETE 
    FROM member_user_salt_password 
    WHERE member_id = mid 
    AND user_id = uid;
END

6.2.2. insert_a_member Table of Contents

insert_a_member is referenced by MemberLogin.insert_a_member to insert a new member into the Member_DB.member_user_salt_password table. MemberLogin.insert_a_member returns true, if the insertion is successful; otherwise, false.

PROCEDURE `insert_a_member` ( IN `member_id` VARCHAR ( 16 ), 
                              IN `user_id` VARCHAR ( 64 ), 
                              IN `salt` VARCHAR ( 12 ), 
                              IN `hashed_password` VARCHAR ( 64 ) )
    NO SQL
BEGIN
    INSERT INTO member_user_salt_password ( member_id, 
                                            user_id, 
                                            salt, 
                                            hashed_password )
    VALUES ( member_id, 
             user_id, 
             salt, 
             hashed_password ); 
END

6.2.3. member_exists Table of Contents

member_exists is referenced by MemberLogin.member_id_verified to verify that a visitor-supplied member ID exists in the Member_DB.member_id table. MemberLogin.member_id_verified returns true, if the supplied id is found in the collection of member ids in the Member_DB.member_id table; otherwise, false

PROCEDURE `member_exists` ( IN `id` VARCHAR ( 16 ) )
    NO SQL
BEGIN
    SELECT * FROM member_id WHERE member_id = id;
END

6.2.4. member_id_already_in_use Table of Contents

member_id_already_in_use is referenced by MemberLogin.member_id_already_used to determine if a visitor-supplied member ID already exists in the Member_DB.member_user_salt_password table. MemberLogin.member_id_already_used returns true, if the supplied id is found; otherwise, false.

PROCEDURE `member_id_already_in_use` ( IN `id` VARCHAR ( 16 ) )
    NO SQL
BEGIN
    SELECT member_id 
    FROM member_user_salt_password 
    WHERE member_id = id;
END

6.2.5. retrieve_salt_hash Table of Contents

retrieve_salt_hash is referenced by MemberLogin.salt_hash_retrieved to retrieve the salt and hashed password associated with the specified user id from the Member_DB.member_user_salt_password table. MemberLogin.salt_hash_retrieved returns true, if the retrieval is successful; otherwise, false. MemberLogin.salt_hash_retrieved returns the salt and hashed password in the global variables stored_salt and stored_hashed_password, respectively.

PROCEDURE `retrieve_salt_hash` ( IN `id` VARCHAR ( 64 ) )
    NO SQL
BEGIN
    SELECT salt,
           hashed_password 
    FROM member_user_salt_password 
    WHERE user_id = id;
END

6.2.6. user_already_exists Table of Contents

user_already_exists is referenced by MemberLogin.user_already_exists to determine if the specified user id is found in the Member_DB.member_user_salt_password table. MemberLogin.user_already_exists returns true, if the supplied user id is found in Member_DB.member_user_salt_password; otherwise, false.

PROCEDURE `user_already_exists` ( IN `id` VARCHAR ( 64 ) )
    NO SQL
BEGIN
    SELECT user_id 
    FROM member_user_salt_password 
    WHERE user_id = id;
END

7. JavaScript Table of Contents

JavaScript is truly the "glue" that holds all of the components of the Login Project together.

There are two important external JavaScript files.

  • SHA256.js
  • member_login.js

7.1. SHA256.js Table of Contents

SHA256.js was derived from the Web Toolkit [^] snippet library. That script contains a generator for an almost-unique 256-bit (32-byte) signature for a given text. The reader is referred to SHA-2 [^] for details. SHA256.js is referenced by a method contained in member_login.js.

The result of applying the SHA256 algorithm is not an encryption. The result cannot be "decrypted". It is referred to as a "one-way" or "trap door" operation. This makes it well suited for the generation of hashed passwords. The function in MemberLogin that generates the hash is

C#
hashed_password ( password,
                  salt )

where password is the plain text password to be hashed and salt is a string containing a random string obtained from invoking the function string_salt.

C#
hash = SHA256 ( salt + password );

Prefixing (or suffixing) a salt [^] to a password is a standard practice when generating hashed passwords. hashed_password returns hash and the invoking function saves both salt and hash in the database.

7.2. member_login.js Table of Contents

The contents of member_login.js are used extensively in the Login Process. member_login.js is constructed as a module of utility functions that support member login. The module is named MemberLogin. MemberLogin exports the following public properties (entry points):

  • already_logged_in
  • clear_error_message
  • hashed_password
  • initialize_login
  • insert_a_member
  • is_eligible
  • login_popup
  • login
  • password_hide_show
  • random_string
  • register_popup
  • register
  • reset_password
  • reset_popup
  • return_to_referrer
  • session_variables
  • set_keyboard_focus_to_id
  • string_salt

There are three major popups in the Login Project.

  • Login
  • Register
  • Reset Password

There are two ancillary popups in the Login Project.

  • Member ID
  • Instructions

Each of these popups is found in a separate popup <div> of the member_login.html page and each popup (except Instructions that is self contained) invokes one or more functions in member_login.js.

In addition, there are six PHP functions

  • delete_a_member
  • insert_a_member
  • member_exists
  • member_id_already_in_use
  • retrieve_salt_hash
  • user_exists

7.2.1. login popup Table of Contents

login flow

 

7.2.2. register popup Table of Contents

registration flow

 

7.2.3. reset_password popup Table of Contents

reset flow

 

7.2.4. member_id popup Table of Contents

member id flow

 

7.2.5. instructions popup Table of Contents

instructionsflow

 

7.3. PHP Table of Contents

The PHP functions provide an interface between JavaScript and the database SQL procedures. The interactions are depicted in the following diagram.

javascript PHP SQL table

In all the PHP functions discussed in the following sections, the required connection string is made up of the database server name, database user name, database password, and database name. For obvious reasons, these data items will not be divulged here. Rather, the following will be substituted:

$servername = "server-name"
$username = "database-user"
$password = "database-password"
$database = "database-name"

$connection = mysqli_connect ( $servername, 
                               $username, 
                               $password, 
                               $database );

The hosting service will normally prohibit direct access to PHP files from the Internet.

7.3.1. delete_a_member ( ) Table of Contents

There is no update available in the Login Project. Rather, when user data must be revised, the revision takes place as a deletion followed by an insertion. For example in the JavaScript reset_password ( ) method appears

if ( !member_deleted ( ) )
  {
  set_error_message (
      reset_error_message,
      "Either the Member ID or User ID is not recognized" );
  return;
  }

session_variables.user_id = user_id;
session_variables.salt = string_salt ( 12, "aA#" );
session_variables.hash = hashed_password (
                                    password,
                                    session_variables.salt );
session_variables.password = password;
session_variables.already_logged_in = false;

if ( insert_a_member ( ) )
  {
  login_popup.style.display='block';
  reset_popup.style.display='none';
  }
else
  {
  set_error_message ( reset_error_message,
                      "Failed to reset password" );
  return;
  }

Here, the record of the member whose password is being revised is first deleted and then a new member record is inserted.

From the earlier figure, the JavaScript member_deleted ( ) invokes the PHP delete_a_member ( ) and the JavaScript insert_a_member ( ) invokes the PHP insert_a_member ( ).

<?php // delete_a_member.php

ini_set ( "display_errors", 1 );
error_reporting ( E_ALL );

$q = strval(htmlspecialchars($_GET['member_id']));
$r = strval(htmlspecialchars($_GET['user_id']));

$servername = "server-name"
$username = "database-user"
$password = "database-password"
$database = "database-name"

$connection = mysqli_connect ( $servername,
                               $username,
                               $password,
                               $database );
if ( !$connection )
  {
  die ( "Connection failed: " . mysqli_connect_error ( ) );
  }

$sql = "CALL delete_member('".$q."','".$r."')";
mysqli_query ( $connection, $sql );

echo mysqli_affected_rows ( $connection );

mysqli_close ( $connection );

?>

7.3.2. insert_a_member ( ) Table of Contents

<?php // insert_a_member.php

ini_set ( "display_errors", 1 );
error_reporting ( E_ALL );

$q = strval(htmlspecialchars($_GET['member_id']));
$r = strval(htmlspecialchars($_GET['user_id']));
$s = strval(htmlspecialchars($_GET['salt']));
$t = strval(htmlspecialchars($_GET['hashed_password']));

$servername = "server-name"
$username = "database-user"
$password = "database-password"
$database = "database-name"

$connection = mysqli_connect ( $servername,
                               $username,
                               $password,
                               $database );
if ( !$connection )
  {
  die ( "Connection failed: " . mysqli_connect_error ( ) );
  }

$sql = "CALL insert_a_member('".$q."','".$r."','".$s."','".$t."')"
if ( mysqli_query ( $connection, $sql ) )
  {
  echo "OK"
  }
else
  {
  echo mysqli_error ( $connection );
  }

mysqli_close ( $connection );

? >

An example of the inserted data (less the member_id) is:

user_id      salt          hashed_password
gggustafson  LtNkIOr2ooLR  bb89891c2484965cc638d5eed159bdb795e0c48064d9769698

7.3.3. member_exists ( ) Table of Contents

<?php // member_exists.php

ini_set ( "display_errors", 1 );
error_reporting ( E_ALL );

$q = strval(htmlspecialchars($_GET['q'])); // member id

$servername = "server-name"
$username = "database-user"
$password = "database-password"
$database = "database-name"

$connection = mysqli_connect ( $servername,
                               $username,
                               $password,
                               $database );
if ( !$connection )
  {
  die ( "Connection failed: " . mysqli_connect_error ( ) );
  }

$sql = "CALL member_exists ( '" .$q. "' )"
$result = mysqli_query ( $connection, $sql );

if ( mysqli_num_rows ( $result )  > 0 )
  {
  while ( $row = mysqli_fetch_assoc ( $result ) )
    {
    echo $row [ "member_id" ];
    }
  }
else
  {
  echo "0"
  }

mysqli_close ( $connection );

? >

7.3.4. member_id_already_in_use ( ) Table of Contents

<?php // member_id_already_in_use.php

ini_set ( "display_errors", 1 );
error_reporting ( E_ALL );

$q = strval(htmlspecialchars($_GET['q'])); // member id

$servername = "server-name"
$username = "database-user"
$password = "database-password"
$database = "database-name"

$connection = mysqli_connect ( $servername,
                               $username,
                               $password,
                               $database );
if ( !$connection )
  {
  die ( "Connection failed: " . mysqli_connect_error ( ) );
  }

$sql = "CALL member_id_already_in_use ( " .$q. " )";
$result = mysqli_query ( $connection, $sql );

if ( mysqli_num_rows ( $result ) > 0 )
  {
  while ( $row = mysqli_fetch_assoc ( $result ) )
    {
    echo $row [ "member_id" ];
    }
  }
else
  {
  echo "0";
  }

mysqli_close ( $connection );

?>

7.3.5. retrieve_salt_hash ( ) Table of Contents

<?php // retrieve_salt_hash.php

ini_set ( "display_errors", 1 );
error_reporting ( E_ALL );

$q = strval(htmlspecialchars($_GET['q'])); // user id

$servername = "server-name"
$username = "database-user"
$password = "database-password"
$database = "database-name"

$connection = mysqli_connect ( $servername,
                               $username,
                               $password,
                               $database );

if ( mysqli_connect_errno ( ) )
  {
  printf ( "Connect failed: %s\r\n",
           mysqli_connect_error ( ) );
  exit();
  }

$sql = "CALL retrieve_salt_hash ( '" .$q. "' )";
if ( $result = mysqli_query ( $connection, $sql ) )
  {
  if ( $result- >num_rows  > 0 )
    {
    while ( $row = $result- >fetch_row ( ) )
      {
      echo ( $row [ 0 ] ." ". $row [ 1 ] );
      }
    }
  else
    {
    echo "0";
    }

  $result- >close();
  }
else
  {
  echo "0";
  }

mysqli_close ( $connection );

? >

7.3.6. user_exists ( ) Table of Contents

<?php // user_exists.php

ini_set ( "display_errors", 1 );
error_reporting ( E_ALL );

$q = strval(htmlspecialchars($_GET['q'])); // user id

$servername = "server-name"
$username = "database-user"
$password = "database-password"
$database = "database-name"

$connection = mysqli_connect ( $servername,
                               $username,
                               $password,
                               $database );

if ( !$connection )
  {
  die ( "Connection failed: " . mysqli_connect_error ( ) );
  }

$sql = "CALL user_already_exists ( '" .$q. "' )";
$result = mysqli_query ( $connection, $sql );

if ( mysqli_num_rows ( $result )  > 0 )
  {
  while ( $row = mysqli_fetch_assoc ( $result ) )
    {
    echo $row [ "user_id" ];
    }
  }
else
  {
  echo "0";
  }

mysqli_close ( $connection );

? >

8. Using the Code Table of Contents

The software contained in the download is a version that mocks a database. There are a number of items that must be addressed to modify it to execute in a production environment.

8.1. Modifying the PHP files Table of Contents

As discussed earlier, the supplied PHP files contain dummy values for the connection string

$servername = "server-name"
$username = "database-user"
$password = "database-password"
$database = "database-name"

Each value on the right-hand side of these assignment statements must be replaced by actual values in the executing environment.

8.2. Turning off Mocking Table of Contents

See the section Mocking the Database for details.

8.3. Insuring Login is Required Table of Contents

It is imperative that a visitor who is not a member never be able to access pages in the members only area of the website. This can be accomplished by using the following template for every page in the members only area.

The smallest page in the download is under_construction.html. That page contains the important <div>s and <script>s discussed above.

<!DOCTYPE html >

<html lang="en">

  <head>

    <title>Under Construction</title>

    <meta http-equiv="Content-type" 
          content="text/html; charset=UTF-8" />
    <meta name="viewport" 
          content="width=device-width, initial-scale=1.0" />

    <link rel="icon" 
          type="image/x-icon" 
          href="../Common/Images/favicon.ico"/>

    <link rel="stylesheet" 
          href="../Common/CSS/w3.css" /> 

    <link rel="stylesheet" 
          href="../Common/CSS/members_area.css" /> 

  </head>

  <body class="member-area">

    <div id="contents" 
         class="content output w3-panel centered" 
         style="display:none;">

      <img src="../Common/Images/under_construction.png"
           alt="Under Construction" 
           width="300px" 
           height="300px"
           style="margin-top:10px;"
           />

    </div>

    <script src="../Common/Scripts/cookies.js"></script>
    <script src="../Common/Scripts/member_login.js"></script>

    <script>
      window.onload = 
        function ( )
          {
          if ( MemberLogin.already_logged_in ( ) )
            {
            document.getElementById ( 'contents' ).style.display = 
                                                              "block";
            }
          else
            {
            document.location = "./MemberLogin/member_login.html";
            }
          };
    </script>

  </body>

</html>

Points of interest:

  • The w3.css [^] file has been copied from the web into the file w3.css. This was done to minimize Internet losses. Note that w3.css was chosen from many and can be replaced.
  • The actual web page content is placed within the <div> with an id equal to "content" and with its display attribute set to none. On initial entry, this is what is desired. The page's onload event handler will update the attribute value if required.
  • The two <script> statements
    <script src="../Common/Scripts/cookies.js"></script>
    <script src="../Common/Scripts/member_login.js"></script>
    
    are required for the onload event handler to execute.
  • The onload event handler determines if the visitor has already logged in. This is achieved by testing for the existance of the session cookie set by an earlier login. If the visitor is logged in, the content <div>'s display attribute is set to block otherwise the visitor is redirected to the member_login.html page.

 

9. References Table of Contents

10. Download Table of Contents

The download, at the top of this article, contains all of the files needed to implement the login mechanism described in this article (as well as this article). Its directory structure is:

download directory

I suggest that the ZIP file be downloaded and then extracted into a newly created directory named "Login".

10.1. Mocking the Database Table of Contents

The file member_login.js contains all of the functions necessary to perform logins. However, since many developers do not have PHP and SQL installed on their machines, a database mock has been included in the file mock.js. This file is included in member_login.html by

<script src="../../Common/Scripts/mock.js"></script>

Within each function that accesses a PHP function appears

if ( MOCK )
  {
  return ( Mock.<mock-code-to-execute> );
  }
else

Wherever this fragment appears it begins in column one and is followed by a block of code that is executed in non-mocking mode. If mocking is to be totally removed, the if ( MOCK ) block can be removed, leaving only the code block following the else. For purists, the braces surrounding the else block could also be removed.

The constant MOCK is declared at the top of member_login.js

var MOCK = true;

If mocking is just to be disabled, then this constant need only be set to false.

If mocking is desired, insure that the constant MOCK is set true in the member_login.js file. When members_area.html is entered, the cookie LOGGEDIN_COOKIE_NAME will not exist and the visitor will be directed to member_login.html.

The mocking software accepts odd number from 1 through 19, inclusive, as member_ids. These values may be modified by modifying the initialize_mock_login_project_variables function in the mock.jsfile.

10.2. members_area.html Table of Contents

When testing the login mechanism described in this article, one needs only open members_area.html in the Members directory. If the cookie LOGGEDIN_COOKIE_NAME does not exist, the visitor will be redirected to member_login.html. If the cookie does exist, the following will be displayed.

members area

To allow multiple testing of the login mechanism, a Delete Cookie button is included. To remove the button, code in the members_area.html surrounded by

<!-- start for debugging -->
:
<!-- end for debugging -->

should be removed (including the HTML comments).

11. Conclusion Table of Contents

This article has provided the code necessary to implement a website login mechanism without using third-party software.

12. Development Environment Table of Contents

The Login Project was developed in the following environment:

Microsoft Windows 7 Professional SP 1
Microsoft Visual Studio 2008 Professional SP1
Microsoft Visual C# 2008
Microsoft .Net Framework Version 3.5 SP1

13. History Table of Contents

07/05/2021 Original article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
In 1964, I was in the US Coast Guard when I wrote my first program. It was written in RPG (note no suffixing numbers). Programs and data were entered using punched cards. Turnaround was about 3 hours. So much for the "good old days!"

In 1970, when assigned to Washington DC, I started my MS in Mechanical Engineering. I specialized in Transportation. Untold hours in statistical theory and practice were required, forcing me to use the university computer and learn the FORTRAN language, still using punched cards!

In 1973, I was employed by the Norfolk VA Police Department as a crime analyst for the High Intensity Target program. There, I was still using punched cards!

In 1973, I joined Computer Sciences Corporation (CSC). There, for the first time, I was introduced to a terminal with the ability to edit, compile, link, and test my programs on-line. CSC also gave me the opportunity to discuss technical issues with some of the brightest minds I've encountered during my career.

In 1975, I moved to San Diego to head up an IR&D project, BIODAB. I returned to school (UCSD) and took up Software Engineering at the graduate level. After BIODAB, I headed up a team that fixed a stalled project. I then headed up one of the two most satisfying projects of my career, the Automated Flight Operations Center at Ft. Irwin, CA.

I left Anteon Corporation (the successor to CSC on a major contract) and moved to Pensacola, FL. For a small company I built their firewall, given free to the company's customers. An opportunity to build an air traffic controller trainer arose. This was the other most satisfying project of my career.

Today, I consider myself capable.

Comments and Discussions

 
GeneralMessage Closed Pin
28-Sep-21 1:58
Member 1537418328-Sep-21 1:58 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA15-Aug-21 14:54
professionalȘtefan-Mihai MOGA15-Aug-21 14:54 
GeneralRe: My vote of 5 Pin
gggustafson16-Aug-21 4:52
mvagggustafson16-Aug-21 4:52 
GeneralMy vote of 5 Pin
DrABELL18-Jul-21 3:08
DrABELL18-Jul-21 3:08 
GeneralRe: My vote of 5 Pin
gggustafson18-Jul-21 8:53
mvagggustafson18-Jul-21 8:53 
GeneralMy vote of 5 Pin
Jan Heckman12-Jul-21 0:42
professionalJan Heckman12-Jul-21 0:42 
GeneralRe: My vote of 5 Pin
gggustafson12-Jul-21 6:00
mvagggustafson12-Jul-21 6:00 
GeneralA useful exercise looking at the complexities of an apparently simple activity Pin
John Brett7-Jul-21 7:17
John Brett7-Jul-21 7:17 
GeneralRe: A useful exercise looking at the complexities of an apparently simple activity Pin
gggustafson7-Jul-21 7:46
mvagggustafson7-Jul-21 7:46 
GeneralRe: A useful exercise looking at the complexities of an apparently simple activity Pin
gggustafson7-Jul-21 9:35
mvagggustafson7-Jul-21 9:35 
QuestionShouldn't the salt be stored on the server? Pin
Marc Clifton7-Jul-21 3:18
mvaMarc Clifton7-Jul-21 3:18 
AnswerRe: Shouldn't the salt be stored on the server? Pin
gggustafson7-Jul-21 6:21
mvagggustafson7-Jul-21 6:21 
But that's not quite the algorithm. A visitor enters the login process and enters a username and password. The first step is to lookup the username in the database and retrieve the salt and hashed password stored there. Note that the database is on the server. Next the salt is prefixed to the visitor-supplied password and the hash is performed. The result is compared to the hashed password retrieved from the database. If equal, the visitor is admitted; otherwise, access is denied.

Answer your concern?
Gus Gustafson

GeneralRe: Shouldn't the salt be stored on the server? Pin
Marc Clifton9-Jul-21 13:38
mvaMarc Clifton9-Jul-21 13:38 
GeneralMy vote of 5 Pin
Member 148383966-Jul-21 11:55
Member 148383966-Jul-21 11:55 
GeneralRe: My vote of 5 Pin
gggustafson6-Jul-21 12:11
mvagggustafson6-Jul-21 12:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.