Let me explain what I’ve learned about authentication over the last few days.
There are 2 flavors of authentication that I’ve been exposed to: stateful and stateless auth. They’re really two ends of a spectrum, and they can overlap.
- After providing legitimate user credentials, a session is created in the server’s data store to track and authorize that user.
- In every subsequent request, the client will include the session in its request. The server finds the session in its data store and associates it the appropriate user account.
- The server can invalidate a session and any new requests with that session ID can be denied, flagged or handled however deemed appropriate.
- After providing legitimate user credentials, identifying information about that user is put into an encrypted token and sent to the client.
- In every subsequent request, the client will include that token in the request. The server decodes the token and uses the identifying info inside to associate the request with the appropriate user account.
- The server trusts that its encryption and other precautions have prevented the token from being tampered with.
Both approaches serve to identify the user making a request from the client.
There are a few drawbacks to stateless auth.
- Stateless tokens are more vulnerable to middle man attacks. The goal is to secure each message in a tamper proof bottle, so when those bottles come back to the server, we can assume it’s the same message we originally created. How confident are you your bottle is tamper proof?
- You can’t revoke stateless tokens. If a token is truly stateless, there’s no way to know if it’s invalidated. Obviously nobody is building software that behaves this way, but that means that those who are using ‘stateless’ auth are relying on stateful workarounds to control authentication. Some strategies to invalidate access once granted to a stateless token:
- Expiry dates. Add a short expiration date to access tokens and a refresh procedure to get a new one. The refresh procedure is stateful, so you can prevent that unauthorized party from renewing access to your data. The party that had their access revoked will still be able to query your server until their current token expires, however.
- Set flags that indicate when resources require reauthentication. If a token contained user information for an account that you wanted to shut out, you could flag that account as reauth needed, and the next time the token with that user info came in, access to the resource could be blocked. This relies on accessing state to make that authing decision.
- Add a token blacklist. Check each incoming token against the token blacklist, and if it’s on there, deny the request. This is another stateful solution.
Then, what are the downsides of stateful auth?
- It’s slower. Maybe. Each request relies on looking up a session in the data store. But if your data store is Redis, how slow is that? And how long does it take to decrypt a token or do the other steps necessary to secure your token against the big scary world outside of the server box?
- It’s not sexy. It’s the way things have been done for a long time.
It would appear that I’m about to wash my hands of stateless auth and shake my head disapprovingly at any one that might jump on the stateless bandwagon. Let me flip the script and come to the defense of stateless auth in certain circumstances, though. I think those circumstances can be determined by answering the question, ‘In your app, does a user need to sign out?’ If yes, stick with sessions.
If no, then stateless auth probably meets your needs, and you might even end up with a slightly more performant solution on a large enough scale. Depending on your app’s particularities, you could issue these stateless tokens to a user with appropriate credentials and still have some control over their access to your data by imposing expirations on those tokens. Then the question is, how often should a user have to login to your system? If you’re comfortable only checking their credentials once a week, then this might be a decent solution. A more common strategy is the refresh token strategy mentioned earlier. This limits DB queries to once every 10 minutes or so and can be done programmatically so as to avoid inconveniencing your user (Although, in this situation the user is usually another computer so /shrug )