Storing user credentials is one of the key roadblocks in creating a sessionless web application. Somehow you need to safely identify the user without storing data on the server nor allowing tampering on the client. If you could solve this problem you’d be well on your way to a sessionless application. Encrypted tokens are the solution.
The Basic Problem
Authenticating a client is usually not a big challenge. You simply present a login form and have the user type in their name and password. For a bit of added security you use a secure protocol like HTTPs. On the server you compare the input with the hashed password in the database. If they match then you can allow the user access, if not then you deny access.
That, relatively speaking, is the easy part. The queston now is how you remember that the user has authenticated: how to store their credentials. For each request back to the server you’ll need a unique token of some kind. To transmit this token you have only two options a web application: 1) you can add that token to every URL and form, or 2) you can set a cookie in the user’s browser. Now every time the user makes a request back to the server you’ll receive the token.
Only one piece of your puzzle has been solved however. A token is just a token. It can’t say anything about whether the user has authenticated. On the server you need some way to map the token to validated credentials. One of the most common ways to do this is by using the token as a session identifier. Then you can simply store the credentials in the session data. This a simple and effective technique. It requires a session however.
Without a session
In many cases you may not want a session the server side. There are many reasons for this, the most common of which are performance and reliability. Managing resources consumes resources and load balancing is difficult. Not having a session allows more possibilities. Obviously you may still have some session-like data backed in a database, such as a shopping cart, but for the general interaction with the site you won’t have a session.
Without a session the primary question is how to store credentials. Authentication is still the same and you still produce a token. What goes in that token becomes interesting. Your first approach may be storing the user’s ID directly in the token. From the ID you can easily lookup the user as needed and figure out what permissions they have. There is however a not-so-insignificant problem related to security. Any user can simply modify their ID and gain access as another user!
The way around this security hole is by making the token opaque: the user has no way of reading or modifying the data. This is exactly what the session ID did before, it was an opaque key to a server session. To do this without a session however may sound impossible, but this is exactly what encryption does. If you encrypt the token with a secret key the user won’t be able to read it. This however doesn’t prevent them from modifying it, and despite the encryption they might still get a working key. Thus you need to do a bit more.
Producing an encrypted token
To produce a secure token there are a few things you have to do. First we’ll define secure to mean it can’t be read, it can’t be modified, and it expires at some point. This means that once a user authenticates you’ll need to save three pieces of information in the token:
- A user identifier
- An expiration date
- A message digest
The first item is obvious and we’ve already covered it. The expiration date will simply be a date when this token is no longer valid. To implement the expiration you simply check this value on the server. There is no magic here. If the current date is greater than the expiration date you reject the token.
The message digest is the piece that many people forget. Encryption alone doesn’t actually protect the integrity of a message: a user could modify the token and decryption would still produce data. Now the result of modifying the token will usually be junk, but nothing guarantees that. A determined user could, with a bit of time, produce a valid token. This is where a message digest comes in — you may know this as a hash. Attach one of these to the tokens and it becomes infeasible an attacker will ever produce a valid token.
So now we have the parts we want in our token. We’ll also need our secret key, and depending on the algorithm an initialization vector. Once we have all of this we need to actually get this data to the client. To do this we package it all together with the following steps.
- Create a map storing the user ID and an expiration date
- Serialize that data to produce a byte array
- Produce your digest (hash) of that byte array
- Append your digest to the byte array
- Encrypt the byte array
- URL encode the byte array to get a string
- Use that string as a value in a cookie
Look at the order and notice that our hash is also encrypted. If the hashing was done after encryption then any attacker could also easily produce a valid hash. The above order produces a feasibly secure token. Now when a request is submitted back to the server you undo the above steps as follows.
- Get the cookie value
- URL decode that string into a byte array
- Decrypt the byte array
- Remove the hash from the array
- Produce a new hash on the remaining array
- Fail if the hashes are not equivalent
- Deserialize the byte array to get a map
- Fail if the current time is greater than the expiration in the map
- Return the user ID from the map
In the decoding process you have a few explicit steps where you can fail, but you also have several more implicit checkpoints. Obviously if the data is too small for a hash you fail. You can also check that the deserialized version is the correct type and count its members. If anything is wrong you can fail and reject the token.
Conclusion
Using the described method you can have an authenticated user without needing to have server side sessions. The technique is secure and works with all browsers. Also realize that you could store more data in the token as well, you aren’t limited to just the user id. Be aware however that cookies have length limits. For even more security you can periodically use new secret keys. All in all a great way to identify and store credentials for a user without relying on session data.
If interested I have a working version of this written in PHP. It is part of the ems-php-utils project on launchpad. I have two high level funtions, ems_encrypt_datalink and ems_decrypt_datalink, which perform all steps and take care of some PHP and encryption tidbits.