JSON Web Tokens vs. Sessions
float-middle.com
float-middle.com
With a traditional session, the token is set to expire after some period of inactivity (e.g. one hour). Subsequent requests push out the expiration... so that it's always one hour from the last activity, rather than one hour from initial authentication.
With JWT, the expiration time is baked into the token and seems effectively immutable. To extend the session, you have to either:
1. Re-authenticate from the browser every hour and store a new JWT token, which is kind of an awful user experience, or
2. Renew the JWT token from the server side every hour. At least in Google's implementation, this requires the user to grant "offline access" (another awful user experience)... and you'd need some hacky approach for replacing the JWT token in the user's browser.
So with all the recent discussion about not using JWT for sessions, what do you guys do? Do you simply make users re-authenticate every hour? Is there another magic trick that no one has brought up?
In my own shop, we're using the browser's JWT token as a server-side cache key... and storing the "real" expiration in cache so that it can be extended (or revoked) as needed. I would be interested to know if others take a similar approach, or have issues with that?
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.
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.
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.
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.
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.
They're important for DNS.
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.
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.
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 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.
Typically with sessions the client has a session key. The key gets sent to the server where it looks up the session (via. memory, cache, database, whatever). You can create a new session, validate an existing session, or end a session. All using that key. They only difference between JWT and cookies is JWTs aren't automatically sent with every request. You have to explicitly send them. I believe this is a good thing. It avoids some common attack vectors.
The header method is preferred for security reasons - cookies would be susceptible to CSRF (Cross Site Request Forgery) unless CSRF tokens were used.
Secondly, the cookies can be sent back only to the same domain (or at most second level domain) they were issued from. If the authentication service resides on a different domain, cookies require much more wild creativeness.
As far as putting something in the header, if you're using javascript check out superagent. It's as easy as: request(url).set('SomeHeader', 'SomeValue');
or the latest http fetch api just do: var request = new Request('/users.json', {method: 'POST',
headers: new Headers({'Content-Type': 'text/plain'})
});
fetch(request).then(function() { /* handle response */ });[1]https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch...
The old token can be used to request a new token with an extended expiration, before it timesout. This can easily happen behind the scenes, so it does not affect the user experience at all. The real problem is that you cannot enforce logouts.
If you just accept that you cannot 100% enforce logouts and then it works fine. A logout is performed by the client side code deleting the token. There is no way of knowing that all copies of the token were really deleted. It is imperative that HTTPS is used so that the token cannot be easily stolen.
You could also bind the token to a specific IP but this would fail for devices with dynamic IPs that could change at any time.
So WHY exactly is this better than a simple distributed session store (e.g. Redis or whatever), with a browser header or signed cookie as the cache key? That's the underlying premise of all this recent discussion that eludes me.
It seems an argument could certainly be made that you don't need to use sessions if you don't need sessions. But you kinda DO need sessions if you do need sessions. I think all of these recent blogs hand-wave over that, and much of the recent "JWT instead of sessions" chatter is people just repeating a mantra that they read in blog posts because it seems like the hot new mantra.
You can have a user auth with google and provide you a user id, you can then slap that id into a token and know that you gave that token to the right user and the info inside that token is what you put there. It does NOT let you keep other sessions alive on its own. You won't be able to get around Google's expiry this way if you need a valid auth to Google's APIs.
What it does do is let you auth a user through google once at the beginning of your own app's session, and not have to keep track of session in your own database or hit google multiple times in a user session.
If you're talking about multiple microservices under your own control, then they could all work with your distributed session store just as easily (if not more so).
If you're talking about SSO across multiple third-party services, then perhaps JWT could be a nicer solution than SAML. However, here you're talking about a single-digit percentage of edge cases. Not enough to declare "JWT over sessions" as a general rule.
One possible alternative to enable auto-renewal is to issue a new token with every request, and manually bake in the persistence of the token into your front end client.
In my own system, I have login with facebook, which submits current FB auth tokens with every request, after which I issue my own app token with all the necessary authorization information for my business logic. Whenever the app token expires, I attempt re-authentication using facebook login, and if successful I send back an updated app token. The front end client has logic built in to compare and swap app tokens if they change and persist in sessionStorage.
It's pretty hacky. I'm a little worried about vulnerabilities that I might be introducing. I luck out in that I don't have a public API which would force the client to implement my front end logic. But it works, for now.
You can also have a password_last_changed field on your user model, where any token issued before this date is considered invalid. That was if a user's account somehow gets compromised, all they need to do is change their password and then all of their existing sessions are expired automatically.
I can't think of any good reason for storing the expiration dates of each individual token, although maybe there is a use case somewhere.
So the client can refresh the token when they are close to expire or just auth again after expiration.
You can do the inverse, e.g. instead of storing 'active sessions', you just store 'revoked sessions'.
There it is easy to reauth. I don't think jwt was ever ment to used in browsers..
the jwt is useful as a capability model if you've broken your internal arch into stateless microservices.
In some cases, no security is better than bad security, because at least your users are aware of the insecurity. (Granted, you're protecting against the replay attack - my point still stands for anyone even considering implementing something based on that paper.)
Use less crypto. The less crypto is being used, the fewer mistakes are being made.
When it comes to sessions, generated a secure random 256 bit token and use that as a session id. Store it in a database or in-memory store. Sticky sessions + local session token storage will fix your network latency problems when you start scaling out.
Federation becomes moderately difficult. Perhaps you could store a copy of session id on each node, and when a session id is compromised, proactively reach to all nodes and ask them to purge the session. This allows immediate mitigation for a session id leak, and since it doesn't rely on timeouts there is no vulnerability window for data exfiltratiin upon a breach. And no crypto.
For example, say client A calls service B, using a token signed by service C. Previously, we were using randomly generated session keys, which meant B had to ask C every time. But with JWTs, B can directly verify that the token is genuine without asking C, because it has C's public key.
We still check with C now and then, but that's because the token auto-expires. We use a long-lived master token stored in a cookie to generate new short-lived ones.
What kind of services are you envisioning? Direct-to-database services like Firebase are a horrible idea for a plethora of security-related reasons, and if you control both service B and C yourself, then you either a) use one-time authorization tokens for stateless services or b) exchange a one-time authorization token for a session on a stateful service.
In none of those three cases do you use the token as the session. Tokens are handed out on a single-use, as-needed basis.
Sessions require a central session store; every request has to go to the central session store to check the session's validity. Incurring one session check per API call is bad enough, but when each API call then invokes half a dozen other APIs, you have a problem. Central session stores don't scale with distributed architectures.
I've not heard the term "direct-to-database" before, but we've been doing it for about 6 years, albeit not using Firebase, and it's huge win over the classic "custom, ad-hoc API per use case" methodology. Exposing a shared data layer abstraction between microservices is no different than exposing a more targeted API (i.e. there's conceptually zero difference between "POST /objects/42" and "POST /users/42"), including as far as security is concerned.
[0] http://cryto.net/%7Ejoepie91/blog/2016/06/19/stop-using-jwt-...
Sessions stores scale as much as anything else.
Simple scenario: Let's say user X wants to update document Y, which involves fetching a photo, processing it and storing it as Z. To read Y, we need to check the session. To update Y, we need to check the session. To store the photo, we need to check the session. We're already up to three roundtrips to the session store.
Some interactions require a lot more participants, each of which need to check the store. Over and over again, even though _clearly_ the world hasn't changed the last 300ms. If one API requires 6 calls to its partners, that's 6 times more roundtrips than necessary.
I don't know if you've ever developed microservices, but building this logic into every single microservice would be a lot of work. We have dozens of microservices, written in different languages, so even if we wrote some generic glue as a library, we'd have to write it at least three times (Go, Ruby and Node.js).
It also means (if I understand the design correctly) that the API gateway has to munge every request envelope to add the actual user ID as an HTTP header (or something similar with gRPC), so that the internal microservices can see it, otherwise they'd have to ask the session store, which would defeat the purpose.
This in turn would mean that every microservice has to trust the upstream, which would require some infrastructure to manage (i.e., microservices would no longer be simply exposed to the world; the API gateway would be a special trusted actor). You also now have a dependency on the API gateway, because the microservice wouldn't know how to deal with a session without it.
The service only accepts the request, it doesn't care where the request came from because it implicitly trust the environment to be protected from direct, unauthenticated calls. Request has the user id in it, so the service knows which data is being accessed.
This way we have separated concerns of security from that of state management. This is what I'm arguing for - security without unnecessary public-facing crypto, and separation of authentication from authorization. The problem public-facing crypto is that there are tons of obscure of attacks on it - manipulating individual bits and sending it to the victim often yields information. New exploits come to light every now and again. I was caught with my pants around my ankles when padding oracle attack against signed cookies came out of the clear blue sky: http://robertheaton.com/2013/07/29/padding-oracle-attack/
Now, to keep the session state you have three options:
- Do the same thing you do with JWT: send the data to the user and accept it back. You know the user has been authenticated already, he can only harm his own resources. When services call each other they pass the session state along with the call, same as JWT again.
- Keep the session data in the centralized session store. Which kid of defeats the purpose of eliminating the centralized session store. :) There are many ways to make that scale well for throughput, e.g. once you know the user id you can trivially shard the session store, but I'm not sure about the latency from accessing yet another machine. This needs to be tested though, perhaps it's negligible. But let's keep that out for now, given the scope of the article.
- Keep the session store at the gateway. When the service replies to the request, part of the payload is the new (additional) session data. The gateway strips that part and keeps it to itself. The next time gateway calls a service for the same user it passes the session data along. The service does not need to know anything about the gateway - it receives a request with user id and session state, then returns the result with new/updated session state. The nice thing is that the users never see the session state (shorter url, less data on the wire, lower chance of malformed data), and yet microservices never have to know where it came from. When services call each other they can attach the state along with it. It may get tricky having to pass the sate back and forth along the call chain of various services, but it's the same problem you would have with JWT anyways. The complexity is why I would circle back to sharded session state storage.
Hopefully this answers all of your questions, let me know if it doesn't? It's an interesting conversation, thanks for that.
I'd let microservices talk directly to each other, however, rather than going through the API gateway. This means you reserve the API gateway for external traffic, and trust the internal traffic. You still need to standardize on/configure a set of "context headers" to pass from one services to another so that the identity of the original caller is preserved; but since this won't rely on a session, it can be trusted and treated opaquely by each application.
My earlier point stands, though: Once you deploy something like this, you now have an explicit dependency on the API gateway, because services can never be directly exposed to the world. It impacts local development, too, because now you either talk using session via the API gateway (which you have to run locally) or directly to the service (using whatever identifying information it trusts), and the fact that they are different is a potential point of confusion, especially for someone not familiar with the architecture. ("It isn't working" — "Oh, you need to go through the API gateway.")
Interesting conversation indeed. I wish there were a better forum for such discussions. There's a lot about microservices where I'd love to exchange ideas and good patterns.
I mean most session verification middleware checks the ip address of the request against the address of the session login. If the address's don't match you kill the session and force another login.
I usually just put the session id in a cookie and use https to encrypt the whole stream shrug
I don't understand this position. What do you mean by "fewer mistakes are being made"?
Thus, you reduce both the amount of mistakes and the impact of mistakes by avoiding crypto where possible.
EDIT: Yes, this includes using existing libraries. It's not just the implementation of the primitives themselves where issues occur.
http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-fo...
Edit: Title is Stop using JWT for sessions
You are correct that the lookup doesn't have to be via the database. You could implement a caching system where the cache is invalidated when the user logs out and requires reauthentication. This is the notion of the session. By definition they cannot be stateless.
Stateless authentication is inherently (slightly) less secure than sessions. I think of a blind librarian who gives out keys to the library. Whoever has a key has access. You can put limitations on the timeframe someone has access to the library, but that's it. If your key gets stolen, the blind librarian can't help you as there is no way for him to tell if it's really you.
(This isn't a perfect scheme since a compromised issuer could have been induced to send post-dated tokens. If your need for global logout was to invalidate tokens issued by a compromised issuer, you'll need to blacklist keys as well)
It's effectively a micro-optimization that will have no real effect, but you can do a simple "exists" query when searching the revocation list, and the TTL keeps the collection small.
Not advocating for JWT as it's a silly mess to do everything correctly, but it is possible.
Server-side key invalidation is entirely possible but it would require having a blacklist of disabled keys and comparing every requests against the black list. This would obviously concede the benefits of scale from JWT tokens since you are doing the same thing as server side sessions. However, the black list should be considered only as an escape hatch and need not be enabled at all times. In fact, once all the tokens in the black list expire, the black list itself can be disabled and things go back to the way they were.
JWT doesn't make the claim that it's a new concept, you are assuming as much. It's a standard and as you correctly gleaned and like most other standards, comes with a lot of benefits, best practices, is battle tested and ready-to-use in your favorite frameworks.
It becomes even more useful if you application serves multiple clients such as browsers, iOS applications and so forth because you can hit the ground running without having to reinvent anything.
[headerB64, payloadB64, signatureB64] = jwt.split('.');
if (atob(signatureB64) === signatureCreatingFunction(headerB64 + '.' + payloadB64) {
// good
} else
// no good
}
You really need a constant time compare for the signature, else you leak information about the correct signature in the timing of the response.The unknown data in the signature creating function is a key. The output is a hash. If you were trying to capture the key you would need to guess what the key was to get the correct hash. Each byte you change results in a cascade of changes in the output. You can't get information about the key from a timing attack on a hash function. I think.
"Known" === HMACSHA256(payload, secret)
You already know the left hand side. How would a constant time comparison protect you from a timing attack in this scenario? It wouldn't. The signature is known, the payload is known, but the secret is not known. That means you'd have to attack the secret, which you could even do offline. By iteratively changing the secret, even if you were to get the first half of the hash right, the next character you change would potentially change every single character in the output.JWT/JWS does not concern itself with keeping the token secret or providing encryption. It's only concern is message authentication. If you leak your token, someone else can impersonate you, just like if you'd leaked your session cookie. The only viable attack on the scheme is to brute force (or discover) the secret, which would allow you to sign a message of your choosing. Once you can sign a message as if you were the server, then you may impersonate anyone you choose.
In either case, public or private, an attacker shouldn't be able to create their own tokens at their leisure. Token theft is guarded against using encryption, token creation is guarded against using constant-time compare. Encryption cannot protect timing attacks.
Again, the hash secret is irrelevant in the case of trying to generate a token for a specific user. The signature for a specific user payload is an opaque hash that can be discovered via timing analysis. The secret hash function that was used to produce it is not important if timing information is available.
If you cannot understand this, please I beg you, do NOT design crypto systems. If you'd like to be an even better Good Samaritan, please disclose your identity to the community. This may protect your future employer and their customers from a breach.
First let me say that I believed we were having a decent conversation until this comment right here. You're personally attacking me at this point. I am not designing crypto. In my first message I stated multiple times that those were my thoughts. My identity isn't hidden - if you care to look through my history. Your account is 20 days old, and you're talking to me about making my identity public?
Anyhow, I now understand the attack you've been describing. I misunderstood the attack, thinking you wanted to be able to sign any message of your choosing. Rather, the attack is focused on iteratively changing the signature of a message until the server confirms it. I was wrong, you were right. The spec for JSON Web Algorithms (JWA)[0] confirms this.
[0] https://tools.ietf.org/html/draft-ietf-jose-json-web-algorit...
I think it's particularly unfair seeing as your original reply to me was so brief that I missed the crucial point. Anyhow, we're done here.
My sincere intent was not to offend you but protect you from yourself. I had already offered what I thought was sufficient explanation for the attack and I felt there were no further options.
I did not mean to offend you but since I have I apologize. I don't believe one's self-worth or employability is based on their expertise in computer security but I can see how that can be implied by what I wrote and I was wrong to be careless with my language online. Honestly I am no expert either. I just hope we can all cooperate, put our egos aside, remain humble in our limited knowledge, keep an open mind and stay focused on the facts to build more secure systems.
Peace and well wishes to you and your family.
The trade-off is extra bytes over the wire as the cookie is sent each same-domain request.
The discussion there is rather interesting. The problem of invalidating logins is discussed. I have not found any satisfactory solution to this problem. You can set a timeout on tokens but then the user would have to log back in periodically. If the software can renew the token automatically then there is nothing to stop an attacker with a stolen token from doing the same, indefinitely. Still, in many situations these problems are no worse than compromised session based logins.
There was one was linked in this thread [1], but how did framework authors tackle this domain? Is everything discussed here entirely homegrown, ad-hoc? Is this something that every one of us has to read up on and re-implement every time?
http://research.google.com/pubs/pub41892.html "Macaroons: Cookies with Contextual Caveats for Decentralized Authorization in the Cloud"
(disclaimer: I'm an author of that paper)
"if your application maintained a list of key/algorithm pairs, and each of the pairs had a name (id), you could add that key id to the header and then during verification of the JWT you would have more confidence in picking the algorithm"
This implies there's a security benefit, but I don't understand how it's better than checking the alg parameter against a whitelist. Perhaps if you're using non-standard names for algorithms, that guards against mistakes?
It's compounded by the fact that a lot of web servers (ie. nginx) have a global header size that limits all of your headers together, not just any one header, which means your JWT size limit is nondeterministic, especially if have large-ish cookies in your request.
There's standard ways to include the JWT in the request body, like form encoding, but that doesn't work for GET requests, so in practice everyone uses the Authorization header.
You can include a JWT as an access_token URL parameter, which works in a GET:
https://foo.invalid/bar?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQOnly works with IE > 11, and even IE 11 breaks down sometimes. Also, search engines choke on large URLs, see http://stackoverflow.com/a/417184/1933738
Yes, large URLs are an issue, but not with search engines & access tokens, since search engines shouldn't ever see access tokens. A JWT should be lightweight — if it's big, then it's wrong.
It's like with the PHPSESSID URL parameter before cookie support was widespread. You can very well attach a session to a search-engine bot, and there are explicit hints for appdevs to do so.
You have to pass the tokens somehow, and search engines usually don't run JS.
> A JWT should be lightweight — if it's big, then it's wrong.
A JWT should replace sessions, and I have seen megabyte-sized sesion files on servers. People put an awful lot of stuff into sessions. Especially if application state is contained in the session. Oh, and if your application e.g. stores paths to files in the session and you switch 1:1 to JWT, you will leak server information to the client, which is a security hole.
https://github.com/LeadDyno/intercooler-js/commit/e83a1ff76a...
I'm using a kind of RBAC and storing Roles in the JWT just seems like a bad idea. Header size is one issue, but also there is the problem of granting/revoking a role and having that change reflected immediately (rather than waiting on the token refresh window).
So, now my API requests happen thusly: "Ok, I have a valid token for User X, so I accept that this request came from User X. Now, let's check User X's roles in the database to see if they have permission to perform action Y on resource Z..."
Hmm... I'm not sure this feels right.
RFCs 2692 & 2693 specify a really great, lightweight, way to do that. They resulting certificates needn't be a whole lot heavier than a JWT, and are much lighter-weight than and X.509 certificate. The RFCs also specify an intelligent, domain-neutral algorithm for performing calculations on tags (what JWT refers to as 'claims') and keys.
It's a pretty awesome solution, and there are a lot of good ideas in there. A streamlined version could, I think, end up being competitive with JWTs.
No, it's the other way around: SDSI was deprecated for SPKI, which took a lot of its ideas about naming from SDSI.
> both of which AFAIK haven't been touched in ~20 years (which is fine by me, if the theory and implementation are solid, but SDSI has CORBA/J2EE smells all over the RFC from what I remember. Lightweight, eh...)
SPKI is indeed old, but the fundamental ideas are really good, and some of them (the cert calculus) are timeless. It needs a v2.0 to update the recommended crypto, specify some more use cases and so forth. But it's really, _really_ good, far better than XPKI and extremely capable.
And still pretty lightweight.
All of the resources I've searched for are fairly old, do you have anything more recent that I can read up on? I see a 2006 paper, but not much other than that.
[1] Though I'm well aware that having an open-standard available for a long time doesn't mean squat, as evidenced by Heartbleed-esque bugs.
Edit: Reading the '00 "A Formal Semantics for SPKI" Howell, Katz, Dartmouth. This is what I was looking for.
In particular, I liked the tuple-calculus they define; it can be extended to support just about any permission I can think of (although it does require reversing DNS names, which is slightly ugly).
I have a scheme to release a v2.0 of the standard someday in my copious free time.
You should rotate your keys often enough to alleviate your fears. You probably ought to trust your MAC a lot more than you trust other links in the key-security chain, anyway.
The one place I can see it might be useful, is when you need to give a third-party system some kind of access to your API, as an alternative to storing a secondary hashed field(s) for api keys.
If the token is cracked, it is essentially the account compromise. You probably meant the base64 decode if the token is 'leaked'?
REST? Bearer tokens or OAuth.
Third party trust? JWT.