(There may be some rubber ducking going on here but… anyway )
I’m building a backend API for a mobile app and try to figure out a good way to handle the authentication. I’ve already setup the initial user registration using Comeonin etc. so now I’m trying to settle on some token format to use.
Is there any reason to use a JWT over something like Phoenix.Token?
Both JWT and Phoenix.Token can be verified without having them stored in the DB, but that means that we can’t invalidate the tokens (without invalidating ALL tokens). Right? This feels a bit scary to me. Am I just overly cautious?
To solve this, you have two options (that I know of):
Use a short expiration time
In a mobile app, the user has to sign in often which is annoying.
Store the tokens in the DB and only tokens that are present among the user’s tokens are valid
Why use a JWT or Phoenix.Token if you still check them in the DB? You can just store a random string without data encoded in it or other complex features.
The DB will end up with a lot of stale tokens (may not be a big issue, but still)
What do you use on your APIs?
Why do you use that?
Have you had a look at guardian_db? It has a process GuardianDb.ExpiredSweeper which is meant to clean your expired tokens after x num of minutes as specified in the configuration. I couldn’t get it to work (I have a feeling it was something in my config) so I am trying to set up OpenMaize.
I’ve looked a bit at guardian_db but not that much yet.
But if I use JWTs, which are supposed to be self contained and can be cryptographically verified, then I store them in my DB and must verify that they are present there as well, and on top of that adding the complexity of a sweeper to clean it out…isn’t that defeating the whole purpose of using JWTs? What is the difference in doing that and just store a simple random string instead in the DB with an expiration date?
The more I read about JWT it feels like people have started to use them for the wrong things. Really, they should be used with a relatively short expire time and not for people to authenticate, over longer periods of time, from something like a mobile app.
What would be a good use case for a JWT (as far as I understand)?
Confirm email token/reset password token (could be valid for a few days)
Download token to get access to download a file (valid for a few minutes/an hour)
With JWT perhaps, you could store signing key within your model and issue different tokens with different expiration time(they all gonna be valid for a particular user and contain information for your business logic). Then you can revoke user key without affecting others.
Or issue tokens with short expiry time and renew each time until you see appropriate to stop(e.g. user was inactive a week)
But if you store a signing key in the DB, you need to fetch that for each request. Why not just fetch the token then? What’s the upside of using it?
With just a random string as a token you can just fetch it from the DB (instead of fetching the signing key). Sure, a JWT has built in expiration, but is it worth all the complexity to just avoid adding a datetime field in the DB?
Another thing I’ve been thinking about: When the user changes his/her password it would be nice to invalidate all the user specific tokens. Would it be OK to us the password hash as a salt/signing key? That would automatically invalidate the tokens when the password changes.
This doesn’t work in all situations, but consider that one of the strengths of BEAM is managing state in processes. Why not load the key from the database and keep it in a GenServer’s state, or even just ETS? No need to make the round trip to the DB, and when you write a new key you just kill the old process and let it reload itself from the (now updated) db record.
With this I believe you might not even need a db. Your GenServer is your key store.
I have to say that’s a pretty smart way of looking at it. So all you need to do is create a key for the PID then when the user signs out (Guardian.revoke) You simply call to kill the process?
If all you’re doing is storing the session token, I’d probably reach for ETS first. If you’ve already got a per-user process going (and its truly account-related, not something where you’d just be tacking on some random piece of info) then I’d look at making it a part of that state. But I’d probably still echo it out to a database unless you’re ok with everyone’s session dying when you do a deploy or restart your node for whatever reason, and you’re also ok with the complications that will arise from needing to do session lookup in a cluster (assuming you want to run more than one node in production.)
I don’t want to trivialize the function; session management is one of those things that gets real complicated real fast at the edges. And frankly I’m new enough at this ecosystem that I’d want to have someone more experienced weigh in on what would be idiomatic and proper. But do keep in mind what the draw for BEAM is in the first place: excellent concurrency and state management tools that go far beyond just a record in a database somewhere.
I may have missed something while I was looking into JWTs but here are some reasons why I decided to go with them:
JWTs contain information about the user, to include access rights. You can decode the JWT to see if that user should have access to a specific API route rather than hit the DB for that info.
I won’t tell you what’s best, because I don’t know myself Right now in our app we use simple bearer tokens - is that’s the best? I don’t know, it works for us right now.
I think JWT is fine, if you accept that it’s rather impossible for it to be truly stateless, if you want to be able to invalidate tokens, which is pretty much required for web/mobile apps.
If you use JWT you gain some flexibility, yes you will lose some features as noted above if you use it in stateless fashion (as intended), but at least you have that option should you need it due to some extreme load spike plus it’s fairly easy to have a separate store for tokens and you don’t need to hit db for the user/profile info.
I maintain an authentication library called Openmaize, which is similar in some ways to Guardian.
About the concern about invalidating tokens on logout, I think Guardian might have some mechanism where they do a database lookup to check this (at least it did have this the last time I looked at Guardian). With Openmaize, I’m using a genserver to store invalidated (logged out) JWTs, and on authentication, the state in this genserver is checked before setting the current_user variable.
Regarding your point about short expiration time, with Openmaize, the default expiration time is two hours after being issued - is this too short / too long? This seems like another case where there is a trade-off between convenience and security, and the expiration time will also depend on how valuable the resources are that you are protecting.
I have been looking at openmaize and liking it so far. I did take out bits and pieces that seem to focus on what I wanted and exchanged things like signup mail with Twilio based sms for the key. A lot of work seem to have gone into thinking this through, thanks a lot.
I guess you store the invalidated tokens in some persistent storage as well, and not only in the gen server state? Otherwise you’d loose track of them at each deploy/restart. Also, having a blacklist of tokens seems odd to me since that requires you to keep them forever. Isn’t it better to just have a whitelist instead (which takes us back to regular tokens again )?
Regarding expiration time, it totally depends on your application. Is the token used for confirmation email/password reset? Keeping a user signed in? Different use cases would require different expiration times.
The problem (for me) is that it is the only way to invalidate JWTs whilst using them for their main benefit. What is their main benefit? In my opinion it is that you don’t have to store them anywhere, they can just be validated anyway. As soon as you must keep track of them, they loose their main value and you are probably using them wrongly.
Generate a JWT that is short-lived, for example 5 minutes.
When generating this token, store a random string (let’s call it auth_string) in the database.
Also store this auth_string in the JWT.
On every client-server communication, do the following:
– If the connection is within the five minute expiration time, just extend the expiration of the JWT with 5 minutes
– If the connection is outside the five minute expiration time, check the auth string of the user in the JWT and compare it with the auth_string in the database: if it’s the same extend five minutes. Otherwise: logout.
The auth string in the database will be changed on every, logout, password reset etc. Now you can invalidate users rather easily by changing the auth string. Unless someone is reconnecting every X minutes of course. if an user is active you don’t need to do any (expensive) database lookups.
To extend the token, you’re going to have to change the exp claim and generate a new token on each request, which means a client will have to support receiving and storing a new token on each request. Why not just use the jti claim and produce a nonce on each request, which must be provided by the next request?
Given that your token lifetimes in this scheme are so short, why bother with the database? Use ETS or an existing per-account genserver to store the auth_string in your outline?
This will get a little more complicated if you allow for multiple active logins for a single account (e.g. mobile and web logged in and active at the same time.)