In the first article: A Better Website Login System
I introduced the EE Collaborative Login System
and its intended purpose.
In this article I will discuss some of the design considerations and give a more comprehensive overview of the Login System
. You do not need to know this information in order to implement some or all of the Login System
on your web site. Based on feedback from this article, certain aspects of the Login System
may even change.
"Security by obscurity is no security"
With that in mind, presenting the design considerations and source code to public scrutiny means that someone will find an erroneous assumption or some important detail that was not originally considered. This process of public review, especially on a site with hundreds of subject matter experts, will result in better, more robust, and more secure code.
I previously asserted that the Login System
is actually an Authentication and Authorization system. Authentication being "prove who you are"
by logging in and authorization being "do you have permission to access a given resource."
The user's table of the Login System has a text field called "userRoles
." Each person who registers is assigned the user role of Guest. The web developer or webmaster is free to add a comma delimited set of roles to that field, such as "Member, Moderator, Administrator" which would assign the user three roles. No database access code has been provided to access that field although it will be added as an enhancement in future versions.
How The Login System Works
Ignoring implementation details for a moment I will discuss how the Login System
functions. The web developer has the choice to implement or not implement any portion of the Login System.
Before any person can login they must first have a user account. If the webmaster is not going to manually add users to the database, then the registration form would be used. For security reasons, a user must supply a valid email address and must accept session and permanent cookies from the website. If they fail to meet these minimal restrictions they can not register or login to the website. Why this requirement was made in the Login System will become apparent as you read on.
The database structure
The Login System currently uses 5 tables. These are;
Of these, the last two have been delivered but no code has been written to implement their intended functions. They will be used to provide security questions for the user to answer when recovering a forgotten User ID or Password.
The currently released code is robust and secure in and offers a better alternative to many options available to web developers. That there are future enhancements in the pipeline should not prevent the system from being implemented. In fact, knowing a system is evolving for the better should be reassuring to implementors as they know they are not implementing an abandoned project.
As designed, enhancements will primarily only require a new page being copied over an existing page. Enhancements which require a change to loginGlobals or the database structure will be kept to a minimum unless offer significant improvement or are mandated by security needs.
The users table has the following fields:
ID int Identity not null primary key
dateRegistered datetime not null
userid nvarchar(50) not null
password nvarchar(255) not null
name nvarchar(100) not null
email nvarchar(100) not null
locked char(1) Default 1
deleted char(1) Default 0
The real work of the user's table is during the registration process and you can not log in unless you have a user account so let's examine registration. ID and dateRegistered should be self-explanatory. The UserID field can be up to 50 characters and the password field can be a passphrase of up to 255 characters. As both fields can accept whitespace and some punctuation characters, this can greatly enhance security. (It should be noted that plain text password are never stored in the system but are hashed
with a salt
prior to being inserted into the table. In the present code base, there is no minimum password requirement or password strength measurement. That will be changed in a future version but will only require that the registration page be replaced with a new version.
Region, City, and Country may seem odd, but the registration IP is geo-located and that information is recorded for auditing purposes. It could be very useful to see where a user exists when registering for an account. This can also be used to limit access to the site to users from a specific location, for example a company selling products in only one country may want to prevent registration from users from differing countries.
Similarly, the useragent and website address are simply informational, but may be wanted by some webmasters for various purposes. News allows the user to opt in to receiving newsletters or e-mails about site changes from the webmaster. These fields can be ignored and website and news can be removed from the register page's markup if the web developer does not wish to receive that information.
Email, locked, dateLocked, and token work together at registration to ensure the user is a valid user. (Actually that the user at least supplied a valid email address.) When a person registers they are required to provide a valid email address. Rather than granting immediate access, a cryptographic token is sent to the email address entered during registration. At the same time the new account is marked locked, the dateLocked is recorded and the token stored for later validation.
Upon receiving the email with the validation token, the user must either click a link in the email to send the token back to the website, or copy the token from the email and paste it into the registration validation page. If the user successfully validates their email address, the account is unlocked, and the dateLocked and token fields are set to Null. At that point the user may login to the website.
Now, this is unlikely, but not impossible. A malicious user may attempt to open an account using another person's details. Thus, there is a link in the validation email for a person to click to delete the registration. It should be noted that for audit purposes the account simply has the deleted field set to "1" (one) and the dateDeleted recorded. The record is not removed from the table.
Just to register a user involves three pages! The registration form page, the registration verification page, and the registration delete page. As an added measure of security, the forms in the Login System implement a cryptographic one time use token to prevent CSRF
attacks. This means a user must actually visit the site to load the form in order to get the form token and the form token must be unchanged when the form is submitted. (This token is also stored on the user's computer which is one of the reasons permanent cookies need to be accepted for use of the Login System.
This prevents someone from creating their own form or a specially crafted link to submit registrations. This also means there could be an error involving the form token. The potential errors are;
1. A cookie error, (remember I said the user must accept cookies,)
2. A form timeout issue, (they have 5 minutes to complete any form,)
3. A token error, (the token is empty, invalid, or was tampered with.)
Of the three, the token error could be caused by a user submitting a form, (which consumes the token,) then using their browser's back button to return to the previously submitted form, which would then have an invalid token. Not a common occurrence, but it happens.
The form error page, (yes now a forth page,) supplied with the example code handles the errors quite simply, and in the case of a person who used their browser's back button to return to a previously submitted form, it provides a link so the user can re-enter the form properly and receive a fresh token.
The tokens are sufficiently random that no table was added to track used tokens. This could potentially be a problem and may be changed in future versions. Replay attacks
are not really feasible the way the tokens are created and considering a corresponding cookie must exist, but tracking previously used tokens would add little overhead and increase security.
...that was just registration, we haven't even talked about logins yet. As a matter of fact we have not discussed all the security measures of the registration process.
Coupled with the anti-CSRF token, all form input is validated for correctness. By that I mean input must pass a white list of acceptable characters for the given field. An improperly formed email address would be rejected out of hand. Input is not echoed back to the screen unless it is first HTML encoded. Between filtering and HTML encoding we will have no XSS vulnerabilities
. Likewise the use of parametrized SQL along with the filtering of data virtually eliminates the possibility of a SQL injection
These security mechanisms are used on all Login System pages which display outside data or access the database. Login Assuming a person has validated their email address they are permitted to login. As mentioned, a new user is assigned the role of guest so if the website has need of differing permissions, guest should be a low permission account. It is up to the webmaster to determine and assign greater levels of permissions to an account.
In the global configuration file there is a setting, lg_login_attempts
which is set at 5 which means if a user fails to authenticate in five tries their account is locked. This is where the loginAttempts table comes into play. Let's look at those fields.
ID int Identity not null primary key
loginAttemptLocked char(1) Default 0
If a user knows they have five login attempts before they lockout their account, and if we count the attempts using a session variable, the user only needs to close their browser after 4 attempts, then reconnect to get five more attempts. Clearly this won't suffice. Also, if a malicious user came along and randomly tried guessing userids, (say admin,) and passwords hoping to get lucky that user could also close and reopen their browser to reset the session counter.
For security, we need to track the login attempts in a table so we do not forget how many login attempts were made. Also, we want to not just track the userid but the remote IP as well. By tracking this in a table we can enforce our five attempts before lockout and we are not just locking a user account we are also banning the remote IP.
From a security standpoint this is only fairly good. We have an audit trail for failed login attempts and we can assure a user at some remote IP is limited to our set number of login attempts. But, we need to recognize there are ways to spoof an IP address, (potentially bypassing our protections.)
A malicious user could perform a denial of service on another user's account simply by intentionally attempting enough bad login attempts with that userid until the account is locked. (This is another reason never to echo userids to the browser.) The webmaster can detect potentially malicious behavior if the remote IP address does not match the registration IP address. Of course some users may login from different addresses, (home, work, wireless hotspot,) so that is not a sure sign of malicious activity.
I suggest it is a best practice to inform registered users what the lockout threshold is and allow them to attempt a password recovery before they reach that value. Of course now we just introduced at least two additional pages to our Login System. A recover password page, and a set new password page. Email is not secure so we can't send a new password to the user and I would argue sending a temporary, one time use password is equally bad. As a result we are back to sending a token to the registered email address of the account owner.
This complicates our set new password page as it must first verify the token before permitting the use to set a new password and login.
Some webmasters may decide this is still weak and require the user to call them by telephone and answer certain questions before unlocking the user's account. Note however, when the user initially signed up we did not require more than a valid email address, to which we sent a verification token. Doing the same to recover a forgotten password is no less secure.
Recovering a forgotten userid may call for additional security measures. It is for that reason the securityQuestions and the questions tables exist. The alpha release of the code does not have pages to allow or make a user select and answer security questions. That is scheduled for a later code release.
Again, the webmaster is in control. It is entirely possible that the webmaster will assign user accounts and not provide a registration page. Or a webmaster may allow registrations but not provide a recover password page. That is a perfectly viable solution for a website with a small community of users. Even in that type of environment, to prevent unauthorized access the webmaster must set the login attempts value to a reasonable value.
I have concentrated on the security measures but we need to examine successful logins as well. When a user begins the login process a record is inserted into the login attempts table. If the user successfully logs in, that record must be removed. Failure to do so would eventually lock the user out. If lg_log_logins
is set to true, the successful login is logged in the logins table. This provides an audit trail needed by many sites. The logins table contains the following fields:
ID int Identity not null primary key
There is nothing which should require explanation in this table.
As HTTP is a stateless protocol, user state (logged in/logged out) is generally handled by session management. Session management varies from one web language to another. In PHP when a user successfully logs in, we can assign a new session id. Doing so, over a secure communications channel, such as that provided by SSL, helps prevent Session Fixation
or Replay attacks. While a man-in-the-middle might be able to ascertain the session id of a user who has not logged in and is not using SSL, knowing that session id is of no value after the user logs in, as a new session id is assigned and transmitted only via the encrypted communications channel.
The man-in-the-middle can not use the previously obtained session id to act on behalf of the user and usurp the user's account. Even with SSL, there are possible scenarios where a man-in-the-middle could ascertain the user's session id, but those situations are rare. If we provide adequate defense against XSS vulnerabilities, the user's session id should remain secret.
As a further precaution, should a user attempt to perform a valuable transaction, i.e. making a purchase or changing their account details, it would be reasonable and prudent that the web developer require the user to reauthenticate before accepting the transaction.
It is worth noting that ASP does not allow setting a new session id for a user. As a result the developers adopted a method similar to that suggested by OWASP
involving a cryptographic token and cookie which is transmitted only over the SSL tunnel. Another reason permanent cookies must be accepted by a user.
Having secured the session we are safe in storing the user's state in the session. The Login System sets the following session variables upon login. Session login is set to true, session userid holds the current userid and session name holds the user's name.
Session login is used for granting or denying access to pages as demonstrated in the first article. Session userid can be used to customize dynamic content for the user, (for example showing the user his purchase history,) and can be used as a key in looking up the user's assigned roles in the user table for systems which implement role based activities. Session name can be echoed to the browser's screen were personalization is desired.
Finally, in terms of logins, the supplied login form has a "Remember Me" check box that if checked writes a permanent cookie to the browser. The next time the user visits the login page, the user will be logged in based on the value of the cookie. Since this is weaker than having the user reauthenticate, the cookie value is used to ensure a valid user exists in the user's table and that the user's account is not locked nor the IP banned. Only then is the user authenticated.
This is still a weak point in the Login System only suitable for low value sites. The field should be removed from the mark up of the login form for online shops or sites which have higher security standards. Again, the option is in the hands of the web developer. Robust code is provided should such a function be desired but the web developer only needs to know how to use the backspace key to remove the field.
In fact, in writing this, it occurs to me that future versions should have a configuration setting to disable the "Remember Me" function.
To add yet another page to the Login System, if we allow users to log in we should provide a way to log out. Failing to do so may mean a user's session persists after closing the browser. If the user was on a shared computer, at a library or Internet Cafe, this would potentially allow another person to open a browser and access the site using the persisted user session. To that end, a logout page is provided which destroys all session variables and cookies in use thus positively logging the user out.
Last but not least, a user may wish to change their password or pass phrase. In fact this should be encouraged as a security best practice. As such we provide a change password page. The user must be logged in before the password or pass phrase can be changed and must be able to supply the current password in order to assign a new one.
This may explain why when a user asks for a login page there is usually more that is necessary. I showed how to create a login page quite simply in the first article about the Login System. It is very nearly "Plug and Play." I'm certain some users will need some assistance setting up their initial database or their settings in loginGlobals, but implementing the pages of the Login System are not difficult.
By making both the code and the design considerations public, I know the Login System will become more robust and secure. Before you say it, I'm well aware that several web development platforms or languages have a fine authentication module. That's nice. Did they supply the registration pages, the recover password pages, the change password page? If not, then there is not a complete authentication and authorization system.
With the talent at Experts-Exchange I hope to see others pick up the gauntlet and assist in translations to other languages, help improve the code base, or port the system to other web development languages.
Use the authentication module from your favorite framework, but use the design guidelines presented here to provide the rest of the Login System in a manner that is simple to implement and maintain.
For those implementing the Login System, you will find a link at the Google Code project
to a forum where FAQs will be posted and various aspects of the system can be discussed. Outside of FAQs, the forum is not intended for support. That's what Experts-Exchange provides.