A simple password-less authentication protocol for web sites

Created 1st April, 2007 12:31 (UTC), last edited 5th April, 2007 07:15 (UTC)

Many sites have features that are only accessible after registering a user account on them. These sites include forums, on-line games, corporate intranets and non-public utility sites amongst others.

These sites are typically secured through a system whereby a user chooses both a unique user name and a password that will be used to authenticate them.

The system I propose here is more convenient for users, and in many cases more secure by removing the requirement for the user to choose or remember a password.

The normal web based registration process

A typical sequence for registering and logging on to a web site forum might be the following:

  1. The user chooses to register on the web site.
  2. A registration form captures a minimum of the following:
    • A user name.
    • The desired password.
    • The contact email address.
  3. An email is then sent to the user containing an activation link.
  4. The user clicks on the activation link.
  5. The user can now log in to the site using their user name and password.
  6. The user has access to the site as an authenticated visitor.

Pros and cons

The most obvious thing that the system has going for it is that site administrators & developers and end-users are familiar with it.

The biggest problem is the cost in managing the passwords. Users lose and forget them and many sites fail to either salt and hash, or encrypt the passwords leaving them vulnerable to breaches caused by database theft.

Many users also choose very weak passwords and often re-use the same password for multiple web sites.

If a password is discovered by a third party then the end-user is often unaware of the breach.

The password-less system

This is the proposed system as an end user sees it.

  1. The user chooses to register on the web site.
  2. A registration form captures a minimum of the following:
    • A user name.
    • The contact email address.
  3. An email is then sent to the user containing an activation link.
  4. The user clicks on the activation link.
  5. The user has access to the site as an authenticated visitor.

From a user's perspective the system appears simpler and there is no password to remember. The account however becomes tied to the browser that they are used when they followed the activation link¹ [1For some use cases this is an advantage, for example access to extranets can be tied to a business computer. Simple implementations would limit a user to a single browser at a time, but it is possible to allow multiple browsers.].

A simple implementation

The implementation outlined here is not suitable for real-world use without incorporating at least some of the increased measured described later. It is intended as a simple introduction to the ideas.

There are five basic operations that are required:

  1. Registering a new user.
  2. Issuing a token to a user.
  3. Associating a token with a browser.
  4. Authenticating a user through their token.
  5. Revoking authentication.

The very simple implementation outlined here is open to several exploits which are addressed in the next section, Increasing the security.

Registration

In pseudo-code the registration process is this:

function register( name, email ) {
    if ( FindUserByEmail( Input( email ) ) ) throw "It looks like you're already registered";
    if ( FindUserByName( Input( name ) ) ) throw "Please choose a different user name";
    var user = new User();
    user.name = name;
    user.email = email;
    issue_token( user );
}

Issuing a new token to a user

If the user changes browser, or the token attached to their browser expires then a new token must be issued.

function issue_token( user ) {;
    user.lives = 1;
    user.token = random_hash();
    user.token.expires = now() + 90;
    EmailToken( user );
}
  • The lives tells us how many browsers can be activated with the same token.
  • The token value itself should be either a good quality hash or a random sequence from a good quality random number generator. The pseudo-random number generators common in most programming languages are probably not suitable² [2FOST.3™ currently uses the SHA1 hash of a new GUID together with the server time.].
  • The length of time that a token should be active for depends on the security requirements of the site. In general the shorter the time the harder an attacker will have to work to keep gaining access to authentication tokens, but the more inconvenient it is to end-user who will have to request new tokens more often.

Activation

The activation process sets a cookie on the user's browser. Again in pseudo-code:

function activate_browser( user, token ) {
    if ( !user ) throw "An error occurred during activation";
    if ( user.lives == 0 ) throw "An error occurred during activation";
    if ( user.token != token ) throw "An error occurred during activation";
    if ( user.token.expires < now() ) throw "An error occurred during activation";
    user.lives = 0;
    SetCookie( "name", user.name, user.token.expires );
    SetCookie( "token", user.token, user.token.expires );
}
  • The external error messages as presented to the end-use should always be the same so as to provide a minimum of information to a malicious user.

Authentication

The final part is the authentication.:

function authenticate() {
    if ( FindUserByName( GetCookie( name ) ) ) throw "Not authenticated";
    if ( !user ) throw "Not authenticated";
    if ( user.lives != 0 ) throw "Not authenticated";
    if ( user.token != GetCookie( "token" ) ) throw "Not authenticated";
    if ( user.token.expires < now() ) throw "Not authenticated";
}
  • Again the user should never know why they were unable to authenticate.
  • The expiration of the token can be reset in order to block a user account at any time.
  • The lives are checked in order to ensure that the account has been activated.

Revoking access

A user needs to be able to choose to immediately revoke their authentication, for example by logging off from a site. This will stop any cookie recovered from a computer or copied by a third-party from working.

function authenticate( user ) {
    if ( !user ) throw "Not a user";
    user.token.expires = now();
}

Increasing the security

There are a few simple measures that can be taken that further increase the security of the system.

The token expiration dates

Storing the token expiration on the server immediately stops the use of stale cookies, but a further improvement can be gained through allowing a shorter time frame for the use of the activation link in the activation email.

The following pseudo-code allows the activated browser to be used for 90 days (as before), but the activation email must be used within two days.

function issue_token( user ) {;
    user.lives = 1;
    user.token = random_hash();
    user.token.expires = now() + 2;
    EmailToken( user );
}
function activate_browser( user, token ) {
    if ( !user ) throw "An error occurred during activation";
    if ( user.lives == 0 ) throw "An error occurred during activation";
    if ( user.token != token ) throw "An error occurred during activation";
    if ( user.token.expires < now() ) throw "An error occurred during activation";
    user.lives = 0;
    user.token.expires = now() + 90;
    SetCookie( "name", user.name, user.token.expires );
    SetCookie( "token", user.token, user.token.expires );
}

The email activation code is now only useful for a very short window, but the browser activation is long enough lived not to be too painful for the user.

Forging a cookie from the activation email

The simple implementation allows the cookie content to be built directly from the contents of the activation email. This can be stopped through generating a new token on activation and using that instead from that point forwards. Combining this with the shorter expiration of the email token gives the following:

function issue_token( user ) {;
    user.lives = 1;
    user.token = random_hash();
    user.token.expires = now() + 2;
    EmailToken( user );
}
function activate_browser( user, token ) {
    if ( !user ) throw "An error occurred during activation";
    if ( user.lives == 0 ) throw "An error occurred during activation";
    if ( user.token != token ) throw "An error occurred during activation";
    if ( user.token.expires < now() ) throw "An error occurred during activation";
    user.lives = 0;
    user.token.expires = now() + 90;
    user.token = random_hash();
    SetCookie( "name", user.name, user.token.expires );
    SetCookie( "token", user.token, user.token.expires );
}

Not storing the email address

Even though tokens must be sent via email it is still not a requirement that the email address be stored on the server, at least not in a readable format.

If no email address is stored on the server then a malicious user could input somebody else's user name and their own email address and have a token sent to them. To stop this the system must ensure that the activation token is sent to the right email address, but this can be done by storing a hash of the original email address:

function register( name, email ) {
    if ( FindUserByEmail( Input( hash( email ) ) ) ) throw "It looks like you're already registered";
    if ( FindUserByName( Input( name ) ) ) throw "Please choose a different user name";
    var user = new User();
    user.name = name;
    user.email = hash( email );
    issue_token( user, email );
}
function issue_token( user, email ) {;
    user.lives = 1;
    user.token = random_hash();
    user.token.expires = now() + 2;
    EmailToken( user, email );
}
  • The hash function should be a good quality cryptographic one-way hash.
  • Any theft of the database no longer results in the leak of user's email addresses.
  • Although the email addresses cannot be recovered from the database it is a simple matter to determine if a given email address is in the database. This approach is not good enough to ensure anonymity of users in the case of a compromised database.

Forging cookies from a stolen database

A malicious user who manages to gain access to a copy of the database would be able to build cookies allowing them to masquerade as any user with an active token. This risk can also be mitigated by changing the activation and authentication processes by salting and hashing the tokens stored in the database.

function activate_browser( user, token ) {
    if ( !user ) throw "An error occurred during activation";
    if ( user.lives == 0 ) throw "An error occurred during activation";
    if ( user.token.expires < now() ) throw "An error occurred during activation";
    var salt = random_hash();
    user.lives = 0;
    user.token.expires = now() + 90;
    user.token = hjash( random_hash() + salt );
    SetCookie( "name", user.name, user.token.expires );
    SetCookie( "token", user.token, user.token.expires );
    SetCookie( "salt", salt, user.token.expires );
}
function authenticate() {
    var user = FindUserByName( GetCookie( name ) );
    if ( !user ) throw "Not authenticated";
    if ( user.lives != 0 ) throw "Not authenticated";
    if ( user.token != hash( GetCookie( "token" ) + GetCookie( "salt" ) ) ) throw "Not authenticated";
    if ( user.token.expires < now() ) throw "Not authenticated";
}

Distribution of the authentication token

Up until now it is assumed that the authentication token is distributed by email. This is of course not a requirement. Other distribution methods might include:

  • Short Messaging System (SMS) to a user's mobile phone.
  • A (possibly automated) phone call to the user reading out the code.
  • The use of public/private key encryption on the email to ensure the activation token cannot be read in transit.

Vulnerabilities

This system is still open to a number of vulnerabilities, many that are common to all web sites due to the nature of HTTP (for example man-in-the-middle attacks) and it is also open to a number of vulnerabilities that normal forms-based password authentication is open to.

Gaining access to the email with the activation token

Like systems that email the password to the user, any malicious user who gains access to the email potentially gains access to the user's account.

For password based systems though this access will normally go undetected by the user. Using this password-less system however the attacker cannot gain long term access to the system without quickly alerting the user.

If the attacker steals the user's email the only window of opportunity open to them is between the activation email being sent out and the proper using requesting a replacement activation code as the replacement immediately invalidates the attackers token.

Stealing the cookie from an authenticated browser

Like all cookie based authentication systems this one is also vulnerable to the theft of the cookie from a user's computer. There are a number of ways to mitigate some aspects of this:

  • Couple the cookie to an IP number.
  • Couple the cookie with some information that the browser sends (for example the browser version).
  • Only use session cookies.

None of these are ideal, but each make the job of the attacker a little harder.

Denial of service

One attack that this system is vulnerable to that a password based system is not is a denial of service attack where a malicious user keeps requesting new authorization tokens for another user. In order to do this they must guess the email address that the system has for the other user, often an easy task.

A number of other techniques can be used to mitigate or eliminate the threat depending on the exact security requirements of the site employing password-less authentication.

The simplest way to defeat this attack is to require a separate confirmation (via email) that the reset should occur if a reset has been carried out recently.

Targeting the mailbox

Malicious users can of course try to gain access to the mailbox that the authentication tokens are being sent to. This is no worse than a compromised mailbox where password reminders are sent. On this system however an attacker cannot make use of the authentication tokens without making that use apparent to the mailbox owner.

If the mailbox has been abandoned then an attacker can take over both the mailbox and any other accounts that send authentication tokens to it. Again though this is the same as for sites that email password.

Multiple browsers

Implementing this system so that a user is able to have several browsers authenticated at the same time is more complex to implement and get right.

A naïve implementation (for example simply using a higher number than one for the user's lives) would be incompatible with most of the features to improve the security of the system over the simplest implementation first outlined.

Conclusions

Most web based authentication systems already capture email addresses and will send emails containing password reminders. Because of this the sending of activation tokens in this way does not impose any security vulnerabilities not already present in most web-based authentication protocols.

It should however be easier for many users and has the potential for improved security for most users as well. It is also no harder to implement than a properly implemented password-based authentication system.

I believe that at worst this system is no worse than most password based authentication systems, and at best is more secure.

Acknowledgements

The good parts of the plan are probably copied from other people. The stupid bits are mine.


Categories: