Two Token Authentication Model


[This was written on April 20th, 2000 and posted to the modperl mailing list. It still contains a fair number of relevant observations.]

Re: Implementing security in CGI

Hi, Interesting thread and interesting question. It makes sense to start with the requirements for what it means to implement those secure features. My requirements have an obvious e-commerce bias, and should probably be heavily reviewed by anyone thinking of using this design for online banking or government work.

First, I'd like to introduce the notion of a secure session. Secure sessions have some subtle differences from the traditional notion of the session, and I'll point those out when they appear.

The secure session has the following properties:

*) The user is able to initiate a secure session by providing proper credentials (i.e., a username and password pair) via a login process.

*) The user is able to terminate the secure session via a logout process.

*) Secure sessions must be able to time out automatically.

*) Secure sessions must *never* transmit sensitive data (such as the password) over insecure channels.

*) The secure session, while it requires the use of a secure protocol (such as HTTPS), should not require the use of cookies. Cookies, however, can be employed to extend the functionality of the system.

Additionally, I feel that one of the essential requirements to any enterprise quality, highly scalable, and fault-tolerant system is the ability to store session state on a tier other than the front-end. This usually means storing state in a shared database, but there are other options.

A very effective method of meeting the requirements above is to use a two token architecture. The first token is a user identifier, which can be passed over insecure channels. This user identification token can be used to restore state that isn't particularly sensitive, such as a shopping cart or user preferences. The second token is a secure identifier, which is never passed over insecure channels. The secure identifier is required to access sensitive data, such as credit card data. (It is possible to create an architecture that uses *only* the secure token, but there are significant benefits in terms of flexibility afforded by using two tokens, so I won't go into the one token model here.)

The fundamental goal of the secure token is to a) keep it safe from prying eyes, and b) use it is a requirement for accessing secure data.

Tokens can be passed in one of two ways. First, the token can be passed as part of the URL. Second, the token can be passed in a cookie. I've found it very useful to create an initialization procedure inside applications that check both places for these tokens, and abstract the particular mechanism used to pass them. Note, however, that is important to remember how the token was passed -- if it came from a cookie, it is cosmetic to not pass it around in the URL. I also recommend creating a library used in your templates that has a function to build URLs that automatically append the tokens if necessary. Since it is critical that secure tokens are never passed in the clear, it really helps to have that function, because it can prevent the secure token from being sent via an insecure page in a URL (i.e., only setting secure tokens in URLs that contain "https").

The user identification token is typically used to restore state from the database. It is created whenever a page is viewed and the client has either not provided one (via the URL or a cookie), or the token they provide doesn't exist in the database. This token should then be set in both the URL and in a cookie. On the subsequent page request, if the user identification token is found in a cookie, it is probably safe to stop putting it in the URL. However, if the token is found only in the URL (probably meaning the client does not use cookies) the server should not try writing the cookie again.

The advantage of the cookie is such that the user can insecurely identify themselves across browser sessions. This is perfectly acceptable when used to restore some insensitive state data (such as a name or shopping cart). However, the system still functions without the cookie. The user can now login to associate their client with particular stored user information.

The login process is as follows:

1) The client connects to a secure page that presents a form requesting a username and password. (There has been much discussion about whether a HTTP form that POSTS to a secure page will encrypt the posted data. There is too much ambiguity here -- I recommend using HTTPS on the login form as well.) The ACTION of the form must also be a secure page.

2) On receiving the username and password, the server compares an encrypted version of the password with the stored encrypted password associated with that customer. If the passwords do not match, the user is returned to the login page. (For highly secure sites, storing a count of the number of failed logins may be desirable.)

3) The server compares the client supplied user identification token with the one associated with that username in the database. If they are different, the server should use the procedure outline above to set the new user identification token in the URL and cookie.

4) The server now generates a unique secure token. Using the techniques I mentioned, this should be appended to all links from the page that use HTTPS, as well as set in the cookie. *Important* -- when setting this in the cookie, use the "secure" flag in the cookie string. This will tell the browser never to send the cookie over insecure channels. (I don't know whether to trust the browser here, but it seems there is little choice if you want to use cookies.)

5) The secure token is associated on the server side (preferably on another tier, such as a database) with the user identification token. Additionally, to support secure session timeouts, the current time must be recorded.

The customer is now technically logged in, and will be able to access sensitive pages, providing they provide the proper tokens, and certain other requirements are met. When a sensitive page is accessed, the following requirements must be met:

1) The page must be viewed over a secure protocol (i.e., HTTPS).

2) The client must present a valid user identification token.

3) The client must present a valid secure token that matches secure token that is stored associated with that user identification token.

4) To implement secure session timeouts, the last access time of the stored secure token is compared with the current time. The delta must be less than some predetermined value (such as 15 minutes or one hour).

5) The current time must be stored as the last access time for the stored secure token.

6) If the secure token was not found in a cookie, then all the exit links for that sensitive page should contain the secure token, if and only if they point at another secured page (similar to the login procedure).

If any of these requirements are not met, the user can *not* access the sensitive page. The server can then display the login page instead (or redirect to it over a secure protocol, in the case that requirement #1 was not met).

Obviously, it helps to encapsulate all of those steps inside a simple routine that each page or module can call at the beginning of the process. Even better, enforce some external control that won't even let the particular page's code run unless those requirements are met.

To explicitly logout, the user should simple be presented with a URL that requests that the secure session end. When this happens:

1) The server then erases the stored secure token identified with the user identification token. (Now, step #3 of the sensitive page view requirements will fail on future requests.)

2) As a courtesy, the server should set the secure token in a cookie to null. (This isn't required for security, but it makes the user feel they have logged out. Do this even if you think the client isn't accepting cookies -- some users are manually accepting/declining cookies to test your system.)

3) Optionally, generate a new user identification token and set this in the cookie and URL. (I prefer this approach, because it is very useful for someone accessing your site from a shared computer.)

There are occasions in which it makes sense to clear the secure cookie without being asked to. For example, after a customer on an e-commerce site makes a purchase, you may want to assume that the secure session is over. You can leave the user identification token in effect, however. Additionally, you can clear the stored secure token after a secure session timeout.

Briefly, the advantage to using cookies is that:

a) The user identification token can persist between browser sessions, provided they don't explicitly log out.

b) The user can move between viewing secured and insecured pages. Recall that if the user access an insecure page, the secure token will *not* be passed along in the URL. When they return to a secure page, they will fail check #3, and need to log in again. Cookies, however, will still contain the secure token (which is okay, because they will only be passed to secure pages) and will pass check #3, but perhaps not check #4 (if the timeout has occurred).

Well, that about sums it up. Obviously there is a lot of work involved in correctly supporting secure authentication in a flexible fashion. I've found that most other approaches, even on high profile e-commerce sites, don't come anywhere close to doing it correctly (or securely!). Apologies if I forgot something important here. I welcome any comments on this technique, and how to improve it.

[Offtopic section snipped...]

Take care,

-DeWitt