Well, here goes. I’ve been putting this off for ages but it’s time to finally get in gear and get user auth working.
Now, by user auth I technically mean two things. Firstly, user auth is user authentication – are you who you say you are? Secondly, user auth is user authorization – do you have permission to do what you are asking to do?
What I’m working on right now is user authentication.
User authentication is a somewhat complicated beast because you have to make the login sequence as secure as you can without horribly inconveniencing the user.
What I would really like to do is use SSL so I only have to worry about the security of the database and the scripts, not the security of the transmission line but since an SSL certificate and a static IP from our web host would cost about $50 USD a year, it’s not in the cards right now. Eliminating the possibility of using SSL, it looks like I’ll be using challenge-response authentication.
Basically, how challenge-response authentication works is this:
- The server (in this case the Rocket Surgeons web host) issues a “challenge”. For our purposes, this will be a single-use session id generated by the login script or possibly by the web server itself.
- The client receives the challenge, modifies it in some way that’s known to both the server and the client (for example, combining it with a value both the server and client know, then running it through a one-way encryption scheme, commonly known as “hashing“) and returns this “response” to the server.
- If the response is what the server expects, the session ID becomes the user’s login token. The client can present this token to the server and the server knows that the user is who they say they are.
For a user to log in to the Rocket Surgeons site:
- The server issues the challenge by including a session ID in the login page (or by setting a cookie, if the user has them enabled) and storing the IP address to which the challenge was sent. Unused session IDs will expire after a certain amount of time, meaning the user only has that amount of time to complete the login process. If the session times out, the user can always request a new session ID and try again.
- When the user has typed their information into the login page, JavaScript will be used to check the values on the login page to make sure they’re valid before sending them to the server. This is for the user’s convenience, since they’ll be checked again by the server before being processed – information sent from the user’s client can’t be trusted for a variety of reasons, for example the user may not have JavaScript turned on or they may be using a browser that has a broken JavaScript implementation; the user may also be a jerk who’s trying to break the site on purpose.
- The client’s response for our implementation will be very similar to the one used on LiveJournal, except it will use a different hashing algorithm. The client will calculate the SHA-1 hash of the user’s password, join it to the session id, then run the resulting string through the SHA-1 algorithm again. This hash will be sent to the server, along with the user’s username.
- Once the server receives the information, it will check that the user’s response was correct by performing the “response” calculations itself. Finally, the IP address of the client is checked to make sure that the IP address that sent the correct response is the one to which the challenge was issued. This prevents malicious users from logging in as somebody else by sending data they “overheard” to the server, which is called a replay attack.
- In-use session IDs expire after the user has been inactive on the site for a specific amount of time, unless they’ve explicitly asked to stay logged in indefinitely and they have cookies enabled. The server will also check that the user’s session ID and IP address match the ones they used to log in for every action that requires authentication. This prevents malicious users from being logged in as another user by copying a session ID and sending it to the server.
There are three drawbacks to this approach.
The first drawback is that password hashes can’t be “salted” in the database. Salting a hash is basically just mixing the data you want to hash with a known (but random) value before running it through the hashing algorithm. Salting hashes helps prevent certain types of attacks against the password database. Since every user has their own “salt”, even if two users pick the same password, the hashes of their passwords will be different, meaning that Alice has no way to tell from the hash itself if her password is the same as Bob’s. Salting also helps against dictionary attacks, where an attacker guesses at what the password could be using a list of words. The attacker would have to know the salt of every user whose password he wanted to break.
The second drawback of challenge-response authentication has to do with initially setting a password – in order to figure out the correct response to a challenge, the server has to have the user’s hashed password. In order to have the user’s hashed password, the user has to send it to the server. If an attacker were listening at just the right time, it would be possible to steal this hashed password while it was being sent and use it to log into the system.
The third (and most important) drawback of challenge-response authentication is that it does nothing to prevent a type of attack called a “man-in-the-middle” attack, where an attacker pretends to be the server while talking to the client and pretends to be the client while talking to the server. The client will happily send all the information a man-in-the-middle needs to break into the user’s account, thinking it is talking to the server. All the man-in-the-middle has to do is pass messages between client and server and wait for the password information to be sent. Once the man-in-the-middle logs in, they can change the password on the user’s account and the account is theirs.
The salting problem is not a huge problem since the password database itself is reasonably secure – in order to steal the database, an attacker would have to break into our web host first.
The initial password setting problem is a little bit more worrisome but frankly, we don’t have very many users and it can be worked around by requiring the user to activate their account by e-mail before logging in for the first time.
I know of no real solution for the third problem (except using SSL). For an account to be stolen however, the user’s network would have to be compromised. Since we have so few users anyway, I could always manually reset any compromised accounts. Hopefully by the time the site is starting to get unmanageable numbers of accounts, we’ll have an SSL certificate.
So here goes. We’ll see how long this takes.