Global distributed consistency, the easy way
I run into an interesting scenario the other day. As part of the sign in process, the application will generate a random token that will be used to authenticate the following user requests. Basically, an auth cookie. Here is the code that was used to generate it:
This is a pretty nice mechanism. We use cryptographically secured random bytes as the token, so no one can guess what this will be.
There was a serious issue with this implementation, however. The problem was that on application startup, two different components would race to complete the signup. As you can see from the code, this is meant to be done in such as a way that two such calls within the space of one hour will return the same result. In most cases, this is exactly what happens. In some cases, however, we got into a situation where the two calls would race each other. Both would load the document from the database at the same time, get a(n obviously) different security token and write it out, then one of them would return the “wrong” security token.
At that point, it meant that we got an authentication attempt that was successful, but gave us the wrong token back. The first proposed solution was to handle that using a cluster wide transaction in RavenDB. That would allow us to ensure that in the case of racing operations, we’ll fail one of the transactions and then have to repeat it.
Another way to resolve this issue without the need for distributed transactions is to make the sign up operation idempotent. Concurrent calls at the same time will end up with the same result, like so:
In this case, we generate the security token using the current time, rounded to an hour basis. We use Argon2i (a password hashing algorithm) to generate the required security token from the user’s own hashed password, the current time and some pepper to make it impossible for outsiders to guess what the security token is even if they know what the password is. By making the output predictable, we make the rest of the system easier.
Note that the code above is till not completely okay. If the two request can with millisecond difference from one another, but on different hours, we have the same problem. I’ll leave the problem of fixing that to you, dear reader.
Comments
At the point they know the password, they can just sign in to obtain the security token, no need for guessing. Just nitpicking on this one, there's probably many more reasons to use pepper here.
Why would you restrict yourself to just one security token? If you allow for multiple, then even with race condition you are able to send requests - all tokens from 'racy' scenario are valid. You can delete expired tokens just before generating a new one. Theoretically there's a possibility that you will have hundreds of thousands sign-in requests partaking in 'race' scenario, but realistically speaking it will be just a handful, which should not slow down the validation of the tokens drastically on subsequent requests. And obviously in this solution the tokens need not to be time based.
Another one is to use tokens that do not have to be stored in db at all - as simple as base64 encoded expiration date + signature. Or even better , industry standard of JWT tokens.
Anyway, I am pretty sure you're well aware of existence of JWT, so there must be some good reasons why you are implementing your own solution.
I agree with Adrian. Generate a new token every time (not stored in the database), containing relevant information about the user and an expiration date, and sign that token cryptographically (and maybe encrypt it too, if you want the content to be opaque to the user). This way you don't even need to call the database to validate the token; just validate the signature, and expiration date and you know it's valid.
Adrian,
The reason to use a pepper is to avoid brute forcing on the password. For example, you may have a list of common passwords. If you can run that on the same hour, you may be able to get the value easily enough. The idea is to make that a little bit harder.
As for using signatures, you are correct, this would be a better option, but the actual reason I'm doing things this way is that you can't have multiple tokens. In this case, we are talking about concurrency not just between requests, that is also between _servers_. Now, if you end up with the same request on different serves (and database instances) and the output is the same, this is much easier to resolve.
Otherwise, you have to write conflict merge code. Not hard, in this context, but easiest to just avoid.
Thomas,
See my point to Adrian, my point was to make it easier to handle a change that is stored in the database, given that it may happen in multiple concurrent servers that are isolated.
Making it output the same value is simplest in this case. But yes, a token that is a signed date is better in this case.
Comment preview