Hacker News new | ask | show | jobs
by moyok 3659 days ago
For this, you can use refresh tokens and set the JWT expiration to a low interval - say 10 minutes. After every 10 minutes, the JWT expires,authentication fails, and the client uses the refresh token to get a new JWT. To revoke a client, revoke their refresh token. This way, though they won't be logged out immediately, they would be logged out in a max of 10 minutes when they need to refresh again, and find out that their refresh token is no longer valid. The point is that instead of every request touching your DB or cache, only one request does in every 10 minutes.
5 comments

I know this works, and I've used it, but I also find it to be the most aggravating thing about JWT and also OAuth. With OAuth, some sites allow you to refresh a token after it times out (so really the refresh token is the source of truth, defeating the purpose of the OAuth token), and others only allow you to refresh before it times out (forcing a login by the user if they are disconnected too long, or storing their username/password in the system keychain, making that the source of truth and again defeating the purpose of the OAuth token).

Also a timeout of this kind is only security theater, because it may only take moments to vacuum a client's data once a token has been skimmed.

The JWT library I'm using in Laravel blacklists tokens by storing them in their own table, rather than invalidating them. This is self-evidently a pretty bad vulnerability because malicious users could fill up the database, so now the developer has to deal with that scenario.

Put all that together and I think the notion of token expiration is, well, dumb. In fact I think timeouts of any kind are a code smell. They make otherwise deterministic code very difficult to reason about in the edge cases. The best thing to do is encapsulate timeout handling in a refresh layer of some kind, once again defeating the whole purpose of timeouts in the first place.

I have personally experienced the security disadvantage you mentioned. I used my Google login to sign into an email client. I immediately realised that the app was going to store all of my email data in their private servers. I quickly went over to the Google dashboard and deauthorised the app, relieved that they would only have been able to get my first few mails in this time. But the app retained access to my emails, even receiving new emails for some time. Probably because of something similar to a refresh token being revoked, but the access token still being valid. I wanted to stop the app from accessing my e-mails, but could not.

However, despite this disadvantage some applications just cannot afford the load of every single request touching the DB or cache. JWT makes sense for that particular use case when you are willing to make this compromise. Instead of every single request touching the cache, maybe every 1000th request does now, because of the token expiration time.

Another use case is when you need a very simple, stateless way to authenticate users and don't require revocation. Some Oauth providers don't give you the option to revoke access tokens, for example.

> despite this disadvantage some applications just cannot afford the load of every single request touching the DB or cache.

Disagree. This is one of the simplest things alive to distribute. Split the query from the token do the query in the DB and the token lookup is effectively a distributed hash table lookup (assuming the token is, say, a UUID). Once the DB query comes back store the result pending the successful retrieval of the token.

What's difficult is handling something like millions of concurrent video downloads / uploads - not looking up tiny tokens.

tiny tokens, but needed for every single operation
Sure, but the really hot tokens could be cached right next to the DB. Plus how many operations is a person doing per second? If its more than a couple you can batch them pretty easily.
It's not dumb if you do it right.

Think of it this way, the _real_ token is your refresh token. It's stored in your database. You control it and it can be revoked at any time. So, now you build 100 other services, and they all accept this refresh token. Problem is, since you control it so well, every other service now needs to validate that refresh token with the auth service on every request. It would be really nice if we could get around that massive traffic pinch point. So, we create crypto tokens that can be validated by every service without the need to make a network call. As a compromise, we make this new token expire in an hour so that the _client_ needs to validate their refresh token every hour and all our services are freed of from ever directly calling the auth service. Sure, this means that when you log out you're not really logged out for up to an hour, but it's all tradeoffs.

For many applications, not being able to immediately log out is an unacceptable trade-off. If you know your account has been compromised and you need to kill all sessions ASAP, an hour delay is unacceptable.
> The JWT library I'm using in Laravel blacklists tokens by storing them in their own table, rather than invalidating them.

The main rationale for JWTs is that it removes the session store as a point of contention (and secondarily it resolves some xdomain issues that aren't that difficult to work around anyway). If you're going to introduce a new table/cache, you're likely better off just using sessions.

Totally agree about timeout management.

TTLs aren't necessarily a code smell.

They're important for DNS.

The refresh token doesn't defeat the purpose of oauth. The purpose is that the third party needs to check in again to refresh.

This gives the end user the time to revoke the token at the provider without the need to revoke or even trust the third party.

I've captured this exact workflow, and it works really well in practice on mobile and browser (probably anything that can curl with JSON) https://github.com/hharnisc/auth-service
Ehh... but at least for Google's implementation, you won't have a refresh token in the first place if the user doesn't grant "offline access" on a special page that comes up after login.

In our usability testing, we've found that this freaks a lot of people out and reduces adoption. The benefits of basically outsourcing our session management to Google don't outweigh this... so we use JWT for auth only, and then use that token as a session key for our own local solution.

> the client uses the refresh token to get a new JWT

What's the point of using the JWT then? If the refresh token lasts for longer than the JWT, and you need to send it periodically back up to the server to auth the user, why use the JWT in the first place?

The point is that it would reduce the DB/cache load, as the refresh token would need to be verified once in a few minutes or so as opposed to verifying it for every request. Regular requests could be authenticated in the CPU itself without having to go through to the DB/cache layer. This means lower latency and reduced load on the DB/cache.
...then make the JWT last as long as the refresh token.
If the JWT is as long as the refresh token, then what's the point of having a refresh token? You would then probably need to get new refresh tokens then to make the session last longer.

The idea is to make the refresh token last for say a few days, and the JWT for say 10 minutes. Now, every 10 minutes the client needs to use the refresh token to get a new JWT. The maximum time a client can have access to the service without a valid refresh token is 10 minutes. All the requests made in this window of 10 minutes would be deemed authenticated by verifying the JWT, and without having to go through the database or cache.

Now, say a user of a web app clicks "log me out from all my devices". The user's access needs to be revoked from everywhere they are logged in. If you invalidate all their refresh tokens, then in a max of 10 minutes they would be logged out from everywhere, as their refresh tokens would no longer work and the JWT duration is only 10 minutes.

This approach is essentially a mid-way or a tradeoff between using traditional sessions and JWT. "Pure" JWT is stateless and hence cannot support individual session revocation. The only way to invalidate sessions in "pure" JWT would be to invalidate the key or certificate used to sign the JWT, but that would invalidate everyone else's sessions as well and hence is not very practical.

Since with this approach you implement sessions plus JWT, it's more complicated than just using sessions. JWT should be used for such applications when the latency or load benefit is significant enough to justify the added complexity. For applications that do not need session revocation, however, JWTs are a convenient way to implement sessions without needing a DB or cache layer.

It's so that if the JWT is stolen in transit, the thief only has access to the token for a shorter period of time. This is why they should expire quickly. Whether or not you think that matters, is not up for debate. That's how it is.
jwt can be verified by the application without having to make a call to the auth service
I've been working on rolling my own authentication layer in Node/Express lately. Your comment is the first time I understood how refresh tokens work.