Well how about that; I had no idea there was a specific standard for this type of thing nor did I realize the client could specifically request a type of algorithm to be used. That seems incredibly short sighted; why wouldn't you let the server handle that and make the algorithm completely transparent?
> Just use cookies over HTTPS.
Maybe it's because of my experience working in environments where cookies were disabled but I just try to avoid them where possible nowadays for my authentication handling. Instead of storing the equivalent of a session / token id in a cookie I just store it in sessionStorage and include that in requests.
Yes but you can now concentrate make yourself XSS proof right; with an XSS you can still get someones cookies... unless you are talking HttpOnly. But we are probably talking single page webapps here anyway.
It would really help encourage the uptake of better alternatives like libsodium if it were standardized. Just referencing some random library can scare decision makers; whereas referencing an IETF official document or ISO standard makes them just take it as given.
The same problem exists with serialization formats - you have XML and JSON, both of which are standardized and have an "official face", although JSON was not born that way. Google protocol buffers are quite superior in many ways, yet as they are just some product of some company and not an actual standard, decision makers are scared of them.
Technology experts do not get to make all the technical decisions, so standardization matters, even if for the stakeholder feelgood factor!
... yes, but... sidenote, JSON is almost unreasonably easy to grok, and translates well into every web-abstracted language, making it the clear-and-away winner.
At the end of the day, tech doesn't win. Developer experience wins. By the time companies have the resources to fight for every iota of performance, they've already won because they shipped product faster than everyone else --- why? Their developers could move and iterate quickly.
It depends on the security model, but in the long term I think sessions are the way to go. It becomes much easier to differentiate the different clients that connects to the API
I hear where you're coming from... but this is also the bane of developer existence. We all have to accept that, every year, tens of thousands of new developers looking for jobs enter the market. There's such a demand for developers that these people get jobs. So footguns, as much as we like to play high-and-mighty and say, "well, duh, don't shoot yourself in the foot" are a real, existential risk to a lot of companies.
Which to me says that relying on there not being any footguns is wishful thinking. The better recourse, to my mind, is to stress the need for mentorship, so people learn to proactively look out for traps.
A standard that best case doesn't explain the risks properly (so many implementers get it wrong) and worst case prescribes dangerous behavior isn't a very good standard. Especially in a field where many developers are told over and over again to rely on standards, it really should spell out even tiny issues.
EDIT: the secondary spec describing the algorithms is at least clear on the use of none, I missed that at first:
Implementations that support Unsecured JWSs MUST NOT accept such objects as valid unless the application specifies that it is acceptable for a specific object to not be integrity protected. Implementations MUST NOT accept Unsecured JWSs by default.
Still, my point about it missing from RFC7115 stands.
---- Original comment ---------
The standard says you should support NONE as the algorithm and that you should use the algorithm the client sends you, all the while completely failing to mention the issues with that, both in its Security Considerations section (which mentions even more "obvious" things like "use keys with high entropy") and in the description of the algorithm to decode a token (which initial implementers probably relied upon to get to a "correct" implementation). Sorry, that is a failure of the spec as well in my book.
If you spec something with risks, at least mark the critical parts clearly with "point away from foot".
A better standard IMHO would have suggested the API for the decode functions, making it clear that the algorithm used should be whiteli
I don't think the spec meant to read that you must allow the client to be able to forge tokens by accepting tokens issued by it without an algo or signature.
If you issue tokens with none, then you will have to accept them when clients send them back. This is obviously a very bad idea, but that's all th spec says. If the issuer chooses to be insecure, that is a valid choice.
If you issue tokens with a specific algo, and clients send them back with a different or none header, you know they have been forged.
The spec allows issuers to decide whether to use none, it doesn't say you must trust none tokens if you know you didn't issue them.
And the spec doesn't spell it out, and initial libraries implementations thus forgot to include things like "let the user specify which algos to accept". And if common libraries provide simple APIs, users expect that these APIs still provide good security.
A standard promoted as "the standard for secure tokens" should not aim for "You can use the pieces to build a correctly behaving system" or "the spec allows secure implementations", it should aim for "if you use this and follow some spelled-out basic rules you get fool-proof secure tokens" and make wrong usage as hard has possible.
> Why would an issuer ever let a client decide what algo to use?
JWT, like SAML, is made to support separate identity providers and the service providers. In the spirit of generality, this means the identity provider(s) could be from a different vendor, operated by a different organization. E.g., you could let users access their account on your service based a token issued from Google. But that means Google chooses the algorithm, not you!
And it's a standard, so you don't have to write any code of your own. Just import the right middleware for your framework and you're set!
So the temptation is there for library authors to support all the defined algorithms, and just enable everything by default to be as compatible as possible - after all, you can just look at the header to see which algorithm to use!
The spec doesn't govern what applications can and cannot accept, it governs what contents are valid in tokens. 'None' is valid, that means my parser library will accept it, it doesn't mean my application must accept the token as valid.
Example: The fact that my service has an http stack which must parse a cookie header doesn't mean my app must accept its contents as valid. There's a lot of confusion on this thread about which components should/must do what things.
I guess I'm missing something here because it seems like the spec includes an ability that everyone here is saying nobody should ever use. Seems useless, by definition!
As long as the problem is known to the developers and the key is specified, I think the biggest issue of JWT is the lack of session invalidation (that is, if you log out your already emitted tokens are still valid until their expiration), but it's a good tradeoff for not having server sessions.
Exactly. The session invalidation has to happen using a session store or expiry header or something similar. In this regard JWT is not better than cookies.
Session invalidation is possible though, by maintaining a (short) blacklist of tokens on the server. JSON Web Tokens can be given an ID (via the jti claim), and server-side these IDs can be matched against this blacklist. When you log out, you send a request to the service that your current token be blacklisted.
Because JSON Web Tokens are short-lived, the blacklist need only contain tokens valid for validity period plus a few seconds and remains very small (often empty).
If you use JWT to allow authorization on several server, then you do need to distribute this blacklist, so it is not a completely trivial solution. In the simplest scenario you might suffice with only maintaining a blacklist on the server that can refresh tokens (this means that when the token expires, a new one cannot be automatically acquired).
s/database/in-memory-map/g should be fine - and suddenly it's pretty lightweight (subtracting service restarts and a highly available message bus of course :)
In both cases there is a DB somewhere storing the list. The difference is that with the blacklist the server can keep an in-memory cache because it's so small. Sessions don't need to be invalidated atomically so the blacklist can be refreshed every couple of seconds.
Store it in a DB for persistence, but push it out to application memory. If for some reason you expect your blacklist to be very large (maybe, you have a massively popular API?), push a bloom filter of the blacklist instead of the actual list.
Now, you (probably) only absorb the DB hit on blacklisted tokens.
1. As other posters pointed out. The blacklist is probably pretty small and can live in memory on your apps servers. If you have a distributed raft network or something to keep it in sync across nodes, even better.
2. You can avoid checking it against the DB unless the API call is sensitive (example: modifies data).
Yeah, of course you can do these things. I really meant to say, "there now exists server-side state for this" — I'm bothered by how existence of that state defeats the statelessness benefits of signature-based schemes, not the fact that I have to query a remote database.
Oh, and also: "only store a blacklist" does not work if you want to provide the "revoke this app you gave access to a while ago and now it's spamming" functionality like in most social networks.
The none issue was highlighted by Tim McLean 2 years ago [0] and comes up in any trivial search about JWT. Surprised that anyone who chooses to use JWT is still getting caught by it as, as you say, any half decent library mitigates this.
For me, the log out / cross device session management issue seems to force a pattern of short expiry with self refreshing tokens. Commonly used devices feel always logged in, whereas uncommonly used devices end up needing a fresh log in each time.
In terms of invalidation, I think a case-by-case basis is best, as it often is.
For example -
If some critical part of your app depends on a user's account or session being still valid, just do the check on that endpoint call (grab the sub/ID claim from the JWT and hit the DB, or similar).
The rest of the time - viewing stats/feed/whatever, admit that if the user had a valid token issued to them 5 minutes ago, it's probably OK to send them stats without having to check revocation (or whichever benefit of JWT you're exploiting).
session invalidation is actually very easy to implement. Its important to think of it as a process instead of a builtin to the standard.
In most of our implementations we achieve this by differentiating between the session token and a request token. Requests that actually power the app use tokens that are very short lived. Request tokens are generated by the core auth server using the session token. A session can be invalidated at the core auth server which will then refuse to give request tokens to the bearer.
I've been using headerless JWTs for stateless API authentication and authorization (JWT without the first segment), but the work is preliminary and I wonder if I'm doing it wrong.
He didn't say use SESSIONS, he said use COOKIES for the data you would expect to go within a session. They scale just fine. You shouldn't be storing that much data in a JWT either.
* MessageVerifier defaults to SHA1. That hasn't been a good default for a few years now.
* It doesn't support expiry as a claim; you have to check it against the current time manually, and factor in leeway if you want that. Because no one ever screwed up a timestamp check.
* It doesn't support any other verifiable claims, for that matter, so if you want to add e.g. issuer and issued-at, you'll have to do so manually.
* If you're not writing a Rails app, you have to pull in ActiveSupport... or copy code, as you suggest, which seems bad for other reasons. Surely maintaining your own crypto fork is almost as bad as writing your own crypto in the first place?
* To the best of my knowledge, ruby-jwt has not suffered either of the two JWT vulnerabilities discussed in this thread.
2. it doesn't, internal session class does and IMO most apps do not need expire claim.
3. Just put issuer/issued_at/whatever in your object
4. I suggest to look at messageverifier and do your own (and use sha256 hmac). Why? Lets consider 50 lines of code not "maintaining your crypto fork" but merely helpers.
5. JWT has more LOC inside, say header payload is just useless. I believe having OpenSSL::HMAC helpers is better and simple enough to not do it wrong
> 2. it doesn't, internal session class does and IMO most apps do not need expire claim.
If you're generating a token to send to an SPA or native app, you can't rely on any other expiration mechanism. It needs to be embedded in the token or stored server-side in a database-backed session or what have you.
> 3. Just put issuer/issued_at/whatever in your object
You're missing the point that these features would require additional SLOC to verify the "claims."
> 4. I suggest to look at messageverifier and do your own (and use sha256 hmac). Why? Lets consider 50 lines of code not "maintaining your crypto fork" but merely helpers.
By this logic, ruby-jwt isn't crypto, just helpers. It seems to be frowned upon simply because it includes additional features some people may not need, and those features and alternative signing methods require more SLOC than your presumed "optimal" implementation. Do you see no value in a flexible, reusable library?
> 5. JWT has more LOC inside, say header payload is just useless. I believe having OpenSSL::HMAC helpers is better and simple enough to not do it wrong
How can critics justify the position that people should write their own OpenSSL::HMAC helpers because they can't correctly call the JWT helpers?
Well that's got me convinced, compelling argument old chap.
> and don't scale.
The vast majority of developers in the world are never going to actually need the type of scalability that requires stateless tokens for authentication.
I would have expected a more in-depth analysis of JWT compared to other techniques. As much as I appreciate the effort to warn people away from bad security practices, JWT is not as a technique fundementally broken.
> JSON Web Signatures Makes Forgery Trivial
> 1. Send a header that specifies the "none" algorithm be used
Most JWT libraries require you to explicitly allow the none algorithm. I had to set a very explicit system property to even get the library I am using to accept none! Even so, anyone implementing JWT should make sure that only the algorithms actually used are accepted.
> 2. Send a header that specifies the "HS256" algorithm when the application normally signs messages with an RSA public key.
Being able to use the public RSA key used to sign an RS256 JWT as the key for a (forged) HS256 token requires the server to accept both RS256 and HS256, and (critically) to be able to use a key configured for RSA assymetric signing to validate HMAC SHA256 signatures. I have not been able to reproduce this latter bug with the library I am using, but even if it did, I still check the algorithm field beforehand: if a token claims it is HS256 signed, I use the (private) key configured for that (if HMAC SHA256 signing is allowed in my application); if it is RS256 signed, I use the (public) key configured for that. The library I use doesn't even get a choice in this; it either receives a JWT that claims to be RS256 together with a RSA 2048 public key, or it gets a JWT that claims to be HS256 together with a private signing key exlcusively used for HS256.
The code preforming those checks between the REST-call receiving the JWT and the JWT library is trivial.
This is all assuming that you would place a service configured to accept both HS256 and RS256 tokens at the same time in production.
As with any security standard: don't use crap libraries; do your research; test your service for common vulnarabilities; follow recommendations made by experts; and don't deploy techniques you don't quite grasp in production software.
> JSON Web Encryption is a Foot-Gun
So is TLS, or hashing passwords. Security is hard and requires a lot of reading and grokking (but it is not too hard for any moderately experienced software engineer).
> TL;DR
Really? 'Too long; didn't read' for a handful of paragraphs? I'll grant the author this much; anyone who can't muster the attention span to read that much text without groping around for a single sentence summary shouldn't be implementing (any!) security standards.
JSON Web Token is a well-documented accessible security standard with a lot of comprehensive information available. As with any technique, there are caveats, but these caveats do not discredit the technique as a whole.
>> A lot of developers try to use JWT to avoid server-side storage for sessions.
This is based on what? Sounds like he just made it up. His other claims does not look sound to much more either. I would like to see a more in-depth analysis on the subject, this all looks very hand-wavy to me.
Agreed, the author of the article shouldn't point out a few flaws/bugs that some JWT libraries had in the past and then deduce that the whole standard is broken at a fundamental level.
JWT is not designed to hold sensitive data, it's designed to hold non-sensitive authentication information like usernames, access groups, privilege levels, and other similar non-sensitive identifying information. It's useful because it loosens your reliance on back-end memory stores like Redis to track session data and makes your architecture much cleaner/simpler.
Based on the entire reason JWT is even a thing? Developers love to believe every app they build is going to run at the scale of Facebook to the power of Google times Twitter, and thus needs to run on 10,000 Docker instances spread across 15 data centres around the globe (and soon, one on the moon!).
Relying on server-side sessions is "terrible" because you have to talk to the backend, and you need to keep the data synchronised in a manner that all 10,000 Docker instances can read/write to it instantly. So instead, a new concept was devised, whereby you use these stateless tokens that don't rely on the same server after issuing.
Of course, it's impossible to invalidate them individually, and they're either insecure (available to JS) or stored in a cookie, and thus sent with every request, which means, due to their larger size than regular session cookies, more data on each request.
I wouldn't say it's impossible to invalidate them individually. It's certainly more effort, and it's probably better to have short-lived session tokens and refreshing, but I think it can be done.
E.g. what about a message bus that publishes an invalid token message that is subscribed to by the API-providing systems, so they can maintain a prematurely-expired tokens list?
On the keeping info in Javascript vs keeping it in a cookie issue, I don't understand that so well. If you made the token a private member of an object that was responsible for the calls, would that help? Then no code could access it?
> I wouldn't say it's impossible to invalidate them individually
It's impossible to invalidate individual "stateless" JWT's.
If you have a server-side "blacklist", guess what: you're not stateless any more, because you still need to keep data in sync, and now you're tempted to allow some otherwise unacceptable delay for sync, giving a potential attacker more time with a stolen JWT. Plus, you know, defeating the whole purpose of using JWTs (being stateless).
> On the keeping info in Javascript vs keeping it in a cookie issue, I don't understand that so well.
Cookies can be set HTTP only. They're sent to the browser, and it will send them back when making requests as per usual, but they're not exposed to JavaScript, at all. There is 0 way for malicious (or non malicious) client side code to see these cookies, thus 0 way for malicious javascript to steal one used as a session cookie.
> If you made the token a private member of an object
If the data can be read from the network by your JavaScript, it can be read from the network by their JavaScript.
(Feel free to bail on this at any time if my questions/suggestions become tiresome :))
> If you have a server-side "blacklist", guess what: you're not stateless any more, because you still need to keep data in sync, and now you're tempted to allow some otherwise unacceptable delay for sync, giving a potential attacker more time with a stolen JWT. Plus, you know, defeating the whole purpose of using JWTs (being stateless).
While I agree that it's no longer stateless, JWT's still really useful in terms of not needing a centralised auth/auth provider that everyone has to hit to see if I am who I say I am and whether I'm allowed to call an API. And a message bus is a pretty good compromise between the extremes of big wide systems that share state and microservices that don't talk to anything else.
> Cookies can be set HTTP only ... session cookie.
Thanks - I get what you're saying.
> If the data can be read from the network by your JavaScript, it can be read from the network by their JavaScript.
Yeah. I think I see what you mean. Assuming you don't mean literally "reading data from the network", as I assume the problem isn't the network access but the access to the security info, are you saying that hostile Javascript on the page can read everything and call everything that legitimate Javascript can?
If so, I can't tell the difference between that and - say - a CSRF token, which presumably can also be read by "their" Javascript? How does anything work if you have that mentality?
The vast majority of people don't need the scale that is difficult to achieve with regular server-side sessions, and that JWT claims to "solve". They add complexity to solve a problem most people don't have.
> I can't tell the difference between that and - say - a CSRF token, which presumably can also be read by "their" Javascript
CSRF is about e.g. making a user's browser make a form submission that results in a request which is malicious in some way. CSRF Tokens are embedded in each legitimate form to ensure that the submission received came from a form you control.
If the attacker has JavaScript access to your page, CSRF is not your problem, so CSRF tokens can't help you.
> The vast majority of people don't need the scale that is difficult to achieve with regular server-side sessions, and that JWT claims to "solve". They add complexity to solve a problem most people don't have.
Not really talking about sessions, but I think I see what you're saying.
> If the attacker has JavaScript access to your page, CSRF is not your problem, so CSRF tokens can't help you.
I agree. I'm trying to understand what you were saying about whatever your Javascript has access to, their Javascript does as well. Why should this be a criticism of JWT and not CSRF?
Well, mostly a JWT lets you know the user that is signed in.
A server-side session generally does the same thing, but can be used to store larger amounts of data.
> I'm trying to understand what you were saying about whatever your Javascript has access to, their Javascript does as well. Why should this be a criticism of JWT and not CSRF?
They're unrelated attack vectors.
CSRF is about bad actors producing links and/or forms on a different site to your own, that a legitimate user clicks/submits (either through social engineering or some kind of javascript in their page) causing them to make a request to your server. A CSRF token prevents this because it ensures that form submission requests have come from a form hosted on your server.
In the situation where we're worried about what someone else's JavaScript has access to, it means their javascript is already loaded into your page: a vulnerability with poorly escaped user content, a rogue browser extension, a malicious or compromised CDN, etc.
In that situation, CSRF is irrelevant. The CS in CSRF is "Cross Site" - this is no longer cross site, as the script is running in the context of your own page.
So in this situation nothing we do can prevent them from making requests within the current user session.
But what we can prevent them from doing, is stealing a user identifying token: e.g. a session cookie, by marking it HTTP Only, so the JS environment doesn't see it.
JWT's accessed over XHR/etc and stored in local storage are available to any malicious scripts running, meaning they can grab the user's JWT and send it off to their own server, allowing them to make requests as the user.
If you send JWT's as cookies and mark them as HTTP only, you've defeated the "don't send session cookies with every request" goal of JWT, and the cookie will be bigger than most session cookies.
> Well, mostly a JWT lets you know the user that is signed in.
In my case I'm happy to use non-JWT methods to hold a user's session information (e.g. an HTTP-only cookie) and just want JWT to authenticate with other systems' APIs without needing to centralise auth/auth.
> They're unrelated attack vectors.
Good point. I guess I more just meant that what can malicious Javascript do with endpoints protected by JWT that it can't do with endpoints protected another way.
> JWT's accessed over XHR/etc and stored in local storage are available to any malicious scripts running, meaning they can grab the user's JWT and send it off to their own server, allowing them to make requests as the user.
I guess this answers the above question: the difference isn't in what can be executed in the browser, but what can be shipped to a different server to be used in attacks from there.
To mitigate that, then, how's this setup:
1) User's session is maintained in HTTP-only cookies.
2) Browser can use (1) to request a JWT token (and a refresh key) to hit a 3rd-party API endpoint. The token is valid for 5 minutes.
3) Browser can use the refresh key from (2) to request another JWT token.
Does that pretty much bring it up to parity with using cookies everywhere, while keeping the goal of noncentral auth/auth?
So JWT is bad because there are bad implementations and there are dumb people who shoot their feet^W^W^Wdon't force alg. Seems like doing software development for 13 years leads to serious problems with logic. There is also confusion between sessions and session storage. Meh..
One advantage I think not mentioned by some of the linked articles is that the JWT's claims are readable on the client.
It's a pretty good plus, for me: no additional round-trips to the server to grab key user details, which can be put into claims, or check access levels (via roles, permissions, or other types of claim).
This doesn't discount the disadvantages, of course.. I think as with everything it's a case of the right tool for the job. "Depends on the use case".
We provide an endpoint to check validity with the server, but haven't used it too often. Anything "reasonably sensitive" (or more) doesn't depend on anything like this client-side security.
But, if you're just hiding an additional Delete button on a page based on claims, this comes in handy.
(Edit: in one case, we've used asymmetric keys, i.e. public key so everyone can check integrity. This was a very different use-case to most web apps, though. Overall I'd say if you're carefully checking integrity of something in client-side JS to do something, I think that's probably the wrong approach)
That's exactly what is useful for. Of course access to a resource is determined server-side; JWT simply allows you to adjust the UI to the permissions the user has without any additional calls. If the user changes the JWT he has client-side, he will just get a broken delete button (the server will reject a JWT that has been tampered with).
You've already been to the server once, if you terri-bad app design requires you to go twice that's more your problem than a "feature" of a broken session system.
Fine, then - to rephrase, it conveniently combines claims with an assertion that the user has been authenticated and authorised. You can do it in other ways too, but it's convenient and a designed part of the make-up of JWT.
JWT can help make a system look more secure, for example you store userid, email some token in session store and a customer goes poking around and tells everyone that he can see that inside the inspector (his data), if you obfuscate it with JWT you eliminate false positives but it doesn't make it anymore secure.
I'm really confused by this post, a signed JWT is issued by the identity provider (or API end point) and is then validated again by the API end point when part of an API call, usually as a bearer token in the header. The validation of the signed JWT is done via the API.
The approach I use is to have a 'use once' refresh token (long timeout) and a security token (short time out) and JTIs to hold a list of logged out/invalid (refresh token used twice) security token IDs.
> The approach I use is to have a 'use once' refresh token (long timeout) and a security token (short time out) and JTIs to hold a list of logged out/invalid (refresh token used twice) security token IDs.
Here's what I've never understood about this approach: the browser can send many requests at the same time, over the same (HTTP/2) or different (HTTP/1.1) connections. If, say, six requests hit your backend at the same time, all with the same refresh and security tokens, with four more queued up on the user-agent, and the security token is expired, how do you know:
1. that all ten requests are valid,
2. to revoke the security token once,
3. to generate one new security token,
4. to mark the refresh token as used,
5. to generate one new refresh token?
Is it as simple as granting some leeway on how long the tokens can be used after they expire/are revoked? Do you have some way of serializing requests on the client to prevent this from happening? Or do you assign all ten requests the same "batch" ID and tie them together on the backend somehow? Do you do a preflight request to refresh the security token if it's expired?
So, what should one should use then?
However, It's insecure with a bad client, right?
Beacuse I was going to use http+JWT for microservices communication an internal network, would that be a problem? any tips on what to switch?
"Just use cookies over HTTPS [instead of JWT]" is weird advice. I mean, JWT goes… inside… things like cookies. (or the Authorization header in APIs, of course)
This drives me nuts. To expand on your point, there are a number of separate, debatable design decisions that seem to get conflated all the time:
* Transport: how the session ID or token is shipped between clients and servers (authorization header, cookie headers, or payload).
* Storage: how the session ID or token is stored on clients (cookies, localStorage, sessionStorage, or in memory).
* Statefulness: whether to use a stateless token (with or without a revocation list) or a stateful, server-side session.
* Encryption and/or signing
* Structure/standardization
Examples:
* JWT generates structured, stateless tokens that are signed—and optionally encrypted (JWE)—with the implementor's choice of algorithm. The tokens can be transported and stored by any mechanisms.
* Rack::Session::Cookie (in Ruby) generates unstructured, stateless tokens that are signed—not encrypted—with HMAC. The tokens are transported and stored as cookies.
* Rack::Session::Pool (also in Ruby) maintains an in-memory store of unstructured, stateful sessions. Unsigned and unencrypted session IDs are transported and stored as cookies.
The point being that you can really mix and match. You can even send a session ID in a header and store it as a non-HttpOnly-cookie on the client. Anything goes!
Come on. By all means, criticize flawed implementations containing bugs and security holes, but drop the attention-seeking behavior of screaming loudly about how an entire standard is [insert string of superlatives here related to "worthless" and "broken"]. If you're going to make such incredibly strong claims, your arguments had better be up to snuff.
With good implementations (plenty of which exist), and careful usage (via good coding and design habits), JWT is a fine standard and it can save a solid amount of time when constructing the security portions of a system.
Shouting about how something is 100% flawed and should be cast into the flames may get you plenty of views and outrage cred, but (thankfully) it doesn't say much about the veracity of your analysis.
A lot of people are talking about the "none" algorithm issue, but the more recent vulnerability[0] is more telling: The report to the working group mailing list[1] led to the point that the standard had a "security considerations" section in the RFC, and this particular issue was never covered.
And now there are difficulties around the fact they cannot update an RFC which people will refer to for years.
It's not a vulnerability in one or two libraries - it looks like just about every made the same mistake, which points to something much more broken.
I use JWT in a couple of projects and it never once occurred to me to let the client decide the algorithm. I am not sure what use-case would necessitate something like that.
I don't see any valid arguments in the post. The issues raised are either mis-implementation or misuse of JWT.
All I am getting is "JWT can be misused in such such way that makes your application vulnerable. And neither its standards nor libraries prevent that, so it sucks".
But when is the last time we see any technology successfully prevented people from being silly?
You shouldn't. Simply check that the hash algorithm specified by the client is the one you used when issuing the token. In a side project, I simply hard code the algorithm [1].
> But when is the last time we see any technology successfully prevented people from being silly?
You can never stop someone sufficiently motivated to shoot himself in the foot from doing it. But you can make it harder for those who would do it be accident by providing more safety features - in case of security this is usually seen as a good idea (safe defaults etc.)
But this is the biggest thing with any security-sensitive code or practice!
Do not give people options, do not allow algorithmic flexibility, do not have fallbacks, do not have backward compatibility, do not allow "testing" or "insecure" options, do not have complex state machine behavior.
All of these things are exactly what JWT or other "design by commission" standards like SSL suffer from and they have predictably lead to ongoing, at times unfixable security problems.
I use stateful JWTs for session management, storing them in localStorage. If someone can exfiltrate the token, they will get a week long authorization, as well as some identifiable information (username, name and role).
Probably I can achieve the same overall system with cryptographically secure session cookies, that are persisted in a database, or other store that is accessible across multiple servers. I guess it would amount to the same thing.
Originally I implemented it because:
* My systems are SPA's. Totally JS dependent from the word go.
* I felt like there would be some advantages to being able to establish certain claims without verification. Say for display purposes prior to server comms (show a list of multiple available sessions for example)...In practise this hasn't really been true. Generally I find in the end I am always checking and verifying anyway - without any huge overhead.
* I've always had a sort of fuzz of uncertainty about Cookies. They always felt a bit out of my hands. Thinking it about rigorously of course, people can switch off JS. They can switch off persistence.
* All my user's local data can be persisted in one place, rather than having to store a reference in the Cookie and then lookup in localStorage. In reality though the code for this is pretty trivial...
So overall while I don't know how right he is, I feel like maybe he has a point. Why not just use cookies?
Maybe it's just because as a JS dev, I want everything to stay within a JS universe...and for some reason Cookies have always felt outside of that to me.
If I can offer some advice in the other direction, don't use cookies.
I tried to do the right thing, use HTTP-only cookies set over an HTTPS endpoint only to find that it's stupidly complicated and has a lot of annoying edge cases. Turns out iOS's webviews don't like them, iOS in general doesn't like them to be on api.hostname.com if the app is on app.hostname.com, you can't validate if you are logged in or not without doing a web request (which is annoying as hell if you are trying to keep a "logged in" state in something like a react app), you need to deal with a bunch of stupid flags to get the damn browser to even let them go across domains, and a hell of a lot of other annoyances that I can't remember right now.
We are most likely moving to something like JWTs (stored in localstorage or indexeddb) soon because of these issues.
If the SPA is doing XHR requests then a localStorage is also an option. It has the advantage that the application can control on which requests the token is being sent, in contracts with cookies where they are sent for any requests on then domain.
We only wanted them assigned to the subdomain that needed them (api.hostname.com), and we set it up that way to make sure we wouldn't accidentally expose cookies to other domains down the line.
I think you should probably have used another common parent domain, like app.web.hostname.com & api.web.hostname.com, with the cookies set for *.web.hostname.com (or something).
Definitely, there's a whole host of potential pitfalls from assigning cookies to the TLD. From the OP's post though it sounded like their issue was one subdomain being unable to access cookies of another subdomain.
"app.hostname.tld" doesn't need to actually access the cookies at all, it just needs to make requests to "api.hostname.tld" which sets the cookies and then later validates them.
Unfortunately safari blocks this use case unless you have also been to "api.hostname.tld" directly and there doesn't seen to be any easy way around it (outside of allowing all 3rd party cookies...)
And while iOS safari now handles this (i think they allow *.hostname.tld to use 3rd party cookies for any other subdomains as long as hostname isn't a common provider or something?) it doesn't seem to work consistently for UIWebView or WKWebView hybrid applications. And the "allow 3rd party cookies" setting doesn't seem to apply to the web views either.
Ahh... yes I'm familiar. I've worked on a couple apps where Apple/Mozilla 3rd Party Cookie polices were a pain point. One option we used was an interstitial page that the user visited briefly hosted on the API layer. Another was switching from cookies to Bearer Tokens which is a whole other bag of worms.
When you say validate login in a react app, what do you mean? Surely the only way to validate a login is to make a request. Or are you saying that your tokens never expire?
Not really validate, just maintain state. (probably should have worded that better...)
Of course the server is going to validate it every request, but it's nicer being able to fail "sooner" on the client side when we know we aren't signed in, or we have never signed in, or our token expired a day ago and we need to re-login, etc...
With HTTPOnly cookies we need to make a request to find any of that out, and when paired with redux and react it's very annoying to have to make a web request to get a small glimpse into what the state really is and try and maintain that in a JS value somewhere AND avoid flashes of incorrect state.
Hell with HTTPOnly cookies you can't even clear it without a web request!
Can't you do "*.hostname.com" asp.net mvc handles it for me and I have subdomains for each customer and they log in and operate in their subdomain. All special cookie flags are configurable so it keeps top security. Getting cookies across domains is something you don't want to do, so I have kind of idea that maybe, probably you are doing something wrong.
These "annoyances" are security features. They're there for a reason. Learn how they work and why they exist. Use them. Stop trying to treat them as bugs that you need to work around.
The fact that an iOS UIWebView doesn't allow you to set 3rd party cookies in spite of what the user allows in Safari's settings is a security feature?
The fact that I can't get an app hosted at app.hostname.tld to send a cookie to api.hostname.tld when both the app requesting it allows credentials to be sent in the XHR request and the server is allowing app.hostname.tld to send credentials with the header Access-Control-Allow-Credentials on all platforms is a security feature?
The fact that I can't purge an HTTPOnly cookie in javascript without making a call to an endpoint is a security feature?
The fact that cookies default to JS readable and work across http and HTTPS and you need to make sure to set flags like "Secure" and "HTTPOnly" or you will be open to all kinds of attacks is a security feature?
The fact that cookies are sent on all requests on that domain and preventing browsers from doing that is what brought me down this path in the first place is a security feature?
Yes, there are security features that cookies give you that are extremely useful, however the downsides, bugs, differing implementations, arbitrary defaults, and the need to know the right set of flags and headers to send to make it secure aren't features. Not to mention that you STILL need to do things like CSRF-Protection to actually secure it.
> So overall while I don't know how right he is, I feel like maybe he has a point. Why not just use cookies?
Because in a highly distributed system hitting a database to validate authorization is expensive and causes bottlenecks.
A cookie that requires you to hit the database does not solve that issue. Although if the cookie was signed some way that can be cryptographically verified then great. But then you are essentially re-implementing something you could be doing in a standard way instead.
> Don't you have to do something similar to invalidate tokens anyway?
Not exactly..
1. Invalidation lists can be held in memory easier than an entire token database. And if the invalidation list is huge you can distribute a bloom filter across your nodes and use that to check before hitting the database.
2. As another poster pointed out. Bearer JWT tokens are meant to be short lived. If your implementation is ontop of OAuth use a longer lived refresh token to get a new bearer token every so often (say half an hour). So if you are OK with your invalidated tokens being OK for "up to" the expiry (so up to half an hour in this example) you only need to do strong validation on the refresh tokens.
cookies have one advantage over localStorage and custom headers though: They can be set by the server in a way that client-side JS code doesn't get to see or change them.
This makes abusing XSS vulnerabilities to get to the token slightly harder.
"Slightly harder" is right. You can always write a non-JS client app, e.g., using Apache HttpClient. At that point the client can do anything it wants with headers.
I probably got down-voted because I conflated cookie with session token cookies. You can of course have a cookie that does not require a database lookup to validate.
But JWT takes care of having the expiry signed in the value (in a cookie the expiry is more of a suggestion that a modified client could ignore). Combine that with low expiry JWT tokens and high expiry refresh tokens (subject to more validation) I think it is a clear winner.
I think the expense of validating authorization to a database can often be worth the cost. Having a dedicated sharded SSD DB system, or other fast cached DB system that is dedicated to checking and validating a cookie/token of a user for each request solves many problems, such as quickly clearing tokens in the case of a hack, and if there is a DB failure on one of these systems then the user simply has to login again and their token/cookie will be stored on another DB in the shard.
The extra overhead on each request of checking these credentials, especially when these requests are hitting the product's database anyway, are often worth the additional security.
Complaining about OAEP when RSA-OAEP is perfectly safe seems needlessly straw-grasping, the other complaints (should) stand perfectly well on their own.
I've used JWT in three languages and the API has always sucked, really badly. I always end up with a verbose heap of gunk - and in some cases, like jwt-go, there is not even a complete example of use in the README + docs. mfw. It should not take multiple steps to sign or verify a signature.
What I've gathered from this post is that the tech community ought to spend more time promoting JWT best practices because misuse can lead to bad times
Lock the encryption to RS512 and the standard is fine. I do a test against that and I also include a key relevant to and content I send with the token. This make the signature per request and considerably more difficult to forge. Maybe using JWTs for sessions is bad, using them for APIs is awesome with the specific caveats.
303 comments
[ 2.8 ms ] story [ 281 ms ] thread> Just use cookies over HTTPS.
Maybe it's because of my experience working in environments where cookies were disabled but I just try to avoid them where possible nowadays for my authentication handling. Instead of storing the equivalent of a session / token id in a cookie I just store it in sessionStorage and include that in requests.
Trading in one security issue for another makes no sense. Just implement the correct mitigation against CSRF attacks; namely, CSRF tokens.
Which is precisely what the blog post that was hyperlinked in the sentence you were responding to was advocating.
If you can show ReactJS XSS then I may need to reconsider.
The same problem exists with serialization formats - you have XML and JSON, both of which are standardized and have an "official face", although JSON was not born that way. Google protocol buffers are quite superior in many ways, yet as they are just some product of some company and not an actual standard, decision makers are scared of them.
Technology experts do not get to make all the technical decisions, so standardization matters, even if for the stakeholder feelgood factor!
At the end of the day, tech doesn't win. Developer experience wins. By the time companies have the resources to fight for every iota of performance, they've already won because they shipped product faster than everyone else --- why? Their developers could move and iterate quickly.
Well.....
Why would an issuer ever let a client decide what algo to use?
"Send a header that specifies the "HS256" algorithm when the application normally signs messages with an RSA public key."
Again, under what circumstances would a header be used by the client to ask for a specific implementation?
What about encrypted client side cookies - would you let the client "send a header" to specify which key to use???
The only problems you highlighted are serious input validation issues and a naive, broken trust model.
If these things are suggested in the standard and promptly followed by major implementations, then the standard isn't very good.
https://www.rfc-editor.org/rfc/rfc7518.txt
Still, my point about it missing from RFC7115 stands.
---- Original comment ---------
The standard says you should support NONE as the algorithm and that you should use the algorithm the client sends you, all the while completely failing to mention the issues with that, both in its Security Considerations section (which mentions even more "obvious" things like "use keys with high entropy") and in the description of the algorithm to decode a token (which initial implementers probably relied upon to get to a "correct" implementation). Sorry, that is a failure of the spec as well in my book.
If you spec something with risks, at least mark the critical parts clearly with "point away from foot".
A better standard IMHO would have suggested the API for the decode functions, making it clear that the algorithm used should be whiteli
If you issue tokens with none, then you will have to accept them when clients send them back. This is obviously a very bad idea, but that's all th spec says. If the issuer chooses to be insecure, that is a valid choice.
If you issue tokens with a specific algo, and clients send them back with a different or none header, you know they have been forged.
The spec allows issuers to decide whether to use none, it doesn't say you must trust none tokens if you know you didn't issue them.
A standard promoted as "the standard for secure tokens" should not aim for "You can use the pieces to build a correctly behaving system" or "the spec allows secure implementations", it should aim for "if you use this and follow some spelled-out basic rules you get fool-proof secure tokens" and make wrong usage as hard has possible.
JWT, like SAML, is made to support separate identity providers and the service providers. In the spirit of generality, this means the identity provider(s) could be from a different vendor, operated by a different organization. E.g., you could let users access their account on your service based a token issued from Google. But that means Google chooses the algorithm, not you!
And it's a standard, so you don't have to write any code of your own. Just import the right middleware for your framework and you're set!
So the temptation is there for library authors to support all the defined algorithms, and just enable everything by default to be as compatible as possible - after all, you can just look at the header to see which algorithm to use!
Right, so, why is this in the spec?
Example: The fact that my service has an http stack which must parse a cookie header doesn't mean my app must accept its contents as valid. There's a lot of confusion on this thread about which components should/must do what things.
As long as the problem is known to the developers and the key is specified, I think the biggest issue of JWT is the lack of session invalidation (that is, if you log out your already emitted tokens are still valid until their expiration), but it's a good tradeoff for not having server sessions.
JWT tokens have the expiration date embedded in the token. There is no way to force it to expire like you you can with cookies.
Although force is a strong word. Even with cookies if you tell the client to delete a cookie it doesn't mean it has to listen.
Because JSON Web Tokens are short-lived, the blacklist need only contain tokens valid for validity period plus a few seconds and remains very small (often empty).
If you use JWT to allow authorization on several server, then you do need to distribute this blacklist, so it is not a completely trivial solution. In the simplest scenario you might suffice with only maintaining a blacklist on the server that can refresh tokens (this means that when the token expires, a new one cannot be automatically acquired).
Now, you (probably) only absorb the DB hit on blacklisted tokens.
1. As other posters pointed out. The blacklist is probably pretty small and can live in memory on your apps servers. If you have a distributed raft network or something to keep it in sync across nodes, even better.
2. You can avoid checking it against the DB unless the API call is sensitive (example: modifies data).
Oh, and also: "only store a blacklist" does not work if you want to provide the "revoke this app you gave access to a while ago and now it's spamming" functionality like in most social networks.
For me, the log out / cross device session management issue seems to force a pattern of short expiry with self refreshing tokens. Commonly used devices feel always logged in, whereas uncommonly used devices end up needing a fresh log in each time.
0: https://www.chosenplaintext.ca/2015/03/31/jwt-algorithm-conf...
For example -
If some critical part of your app depends on a user's account or session being still valid, just do the check on that endpoint call (grab the sub/ID claim from the JWT and hit the DB, or similar).
The rest of the time - viewing stats/feed/whatever, admit that if the user had a valid token issued to them 5 minutes ago, it's probably OK to send them stats without having to check revocation (or whichever benefit of JWT you're exploiting).
Thing is, this at least gives you the /option/..
In most of our implementations we achieve this by differentiating between the session token and a request token. Requests that actually power the app use tokens that are very short lived. Request tokens are generated by the core auth server using the session token. A session can be invalidated at the core auth server which will then refuse to give request tokens to the bearer.
I've been using headerless JWTs for stateless API authentication and authorization (JWT without the first segment), but the work is preliminary and I wonder if I'm doing it wrong.
* It doesn't support expiry as a claim; you have to check it against the current time manually, and factor in leeway if you want that. Because no one ever screwed up a timestamp check.
* It doesn't support any other verifiable claims, for that matter, so if you want to add e.g. issuer and issued-at, you'll have to do so manually.
* If you're not writing a Rails app, you have to pull in ActiveSupport... or copy code, as you suggest, which seems bad for other reasons. Surely maintaining your own crypto fork is almost as bad as writing your own crypto in the first place?
* To the best of my knowledge, ruby-jwt has not suffered either of the two JWT vulnerabilities discussed in this thread.
Finally, why is this "simple":
But this is "bad": They seem fairly equivalent to me?2. it doesn't, internal session class does and IMO most apps do not need expire claim.
3. Just put issuer/issued_at/whatever in your object
4. I suggest to look at messageverifier and do your own (and use sha256 hmac). Why? Lets consider 50 lines of code not "maintaining your crypto fork" but merely helpers.
5. JWT has more LOC inside, say header payload is just useless. I believe having OpenSSL::HMAC helpers is better and simple enough to not do it wrong
> 2. it doesn't, internal session class does and IMO most apps do not need expire claim.
If you're generating a token to send to an SPA or native app, you can't rely on any other expiration mechanism. It needs to be embedded in the token or stored server-side in a database-backed session or what have you.
> 3. Just put issuer/issued_at/whatever in your object
You're missing the point that these features would require additional SLOC to verify the "claims."
> 4. I suggest to look at messageverifier and do your own (and use sha256 hmac). Why? Lets consider 50 lines of code not "maintaining your crypto fork" but merely helpers.
By this logic, ruby-jwt isn't crypto, just helpers. It seems to be frowned upon simply because it includes additional features some people may not need, and those features and alternative signing methods require more SLOC than your presumed "optimal" implementation. Do you see no value in a flexible, reusable library?
> 5. JWT has more LOC inside, say header payload is just useless. I believe having OpenSSL::HMAC helpers is better and simple enough to not do it wrong
How can critics justify the position that people should write their own OpenSSL::HMAC helpers because they can't correctly call the JWT helpers?
Well that's got me convinced, compelling argument old chap.
> and don't scale.
The vast majority of developers in the world are never going to actually need the type of scalability that requires stateless tokens for authentication.
> JSON Web Signatures Makes Forgery Trivial
> 1. Send a header that specifies the "none" algorithm be used
Most JWT libraries require you to explicitly allow the none algorithm. I had to set a very explicit system property to even get the library I am using to accept none! Even so, anyone implementing JWT should make sure that only the algorithms actually used are accepted.
> 2. Send a header that specifies the "HS256" algorithm when the application normally signs messages with an RSA public key.
Being able to use the public RSA key used to sign an RS256 JWT as the key for a (forged) HS256 token requires the server to accept both RS256 and HS256, and (critically) to be able to use a key configured for RSA assymetric signing to validate HMAC SHA256 signatures. I have not been able to reproduce this latter bug with the library I am using, but even if it did, I still check the algorithm field beforehand: if a token claims it is HS256 signed, I use the (private) key configured for that (if HMAC SHA256 signing is allowed in my application); if it is RS256 signed, I use the (public) key configured for that. The library I use doesn't even get a choice in this; it either receives a JWT that claims to be RS256 together with a RSA 2048 public key, or it gets a JWT that claims to be HS256 together with a private signing key exlcusively used for HS256.
The code preforming those checks between the REST-call receiving the JWT and the JWT library is trivial.
This is all assuming that you would place a service configured to accept both HS256 and RS256 tokens at the same time in production.
As with any security standard: don't use crap libraries; do your research; test your service for common vulnarabilities; follow recommendations made by experts; and don't deploy techniques you don't quite grasp in production software.
> JSON Web Encryption is a Foot-Gun
So is TLS, or hashing passwords. Security is hard and requires a lot of reading and grokking (but it is not too hard for any moderately experienced software engineer).
> TL;DR
Really? 'Too long; didn't read' for a handful of paragraphs? I'll grant the author this much; anyone who can't muster the attention span to read that much text without groping around for a single sentence summary shouldn't be implementing (any!) security standards.
JSON Web Token is a well-documented accessible security standard with a lot of comprehensive information available. As with any technique, there are caveats, but these caveats do not discredit the technique as a whole.
This is based on what? Sounds like he just made it up. His other claims does not look sound to much more either. I would like to see a more in-depth analysis on the subject, this all looks very hand-wavy to me.
Based on the entire reason JWT is even a thing? Developers love to believe every app they build is going to run at the scale of Facebook to the power of Google times Twitter, and thus needs to run on 10,000 Docker instances spread across 15 data centres around the globe (and soon, one on the moon!).
Relying on server-side sessions is "terrible" because you have to talk to the backend, and you need to keep the data synchronised in a manner that all 10,000 Docker instances can read/write to it instantly. So instead, a new concept was devised, whereby you use these stateless tokens that don't rely on the same server after issuing.
Of course, it's impossible to invalidate them individually, and they're either insecure (available to JS) or stored in a cookie, and thus sent with every request, which means, due to their larger size than regular session cookies, more data on each request.
So.. that. That is what it's based on.
Edit: added missed word "same".
E.g. what about a message bus that publishes an invalid token message that is subscribed to by the API-providing systems, so they can maintain a prematurely-expired tokens list?
On the keeping info in Javascript vs keeping it in a cookie issue, I don't understand that so well. If you made the token a private member of an object that was responsible for the calls, would that help? Then no code could access it?
It's impossible to invalidate individual "stateless" JWT's.
If you have a server-side "blacklist", guess what: you're not stateless any more, because you still need to keep data in sync, and now you're tempted to allow some otherwise unacceptable delay for sync, giving a potential attacker more time with a stolen JWT. Plus, you know, defeating the whole purpose of using JWTs (being stateless).
> On the keeping info in Javascript vs keeping it in a cookie issue, I don't understand that so well.
Cookies can be set HTTP only. They're sent to the browser, and it will send them back when making requests as per usual, but they're not exposed to JavaScript, at all. There is 0 way for malicious (or non malicious) client side code to see these cookies, thus 0 way for malicious javascript to steal one used as a session cookie.
> If you made the token a private member of an object
If the data can be read from the network by your JavaScript, it can be read from the network by their JavaScript.
> If you have a server-side "blacklist", guess what: you're not stateless any more, because you still need to keep data in sync, and now you're tempted to allow some otherwise unacceptable delay for sync, giving a potential attacker more time with a stolen JWT. Plus, you know, defeating the whole purpose of using JWTs (being stateless).
While I agree that it's no longer stateless, JWT's still really useful in terms of not needing a centralised auth/auth provider that everyone has to hit to see if I am who I say I am and whether I'm allowed to call an API. And a message bus is a pretty good compromise between the extremes of big wide systems that share state and microservices that don't talk to anything else.
> Cookies can be set HTTP only ... session cookie. Thanks - I get what you're saying.
> If the data can be read from the network by your JavaScript, it can be read from the network by their JavaScript. Yeah. I think I see what you mean. Assuming you don't mean literally "reading data from the network", as I assume the problem isn't the network access but the access to the security info, are you saying that hostile Javascript on the page can read everything and call everything that legitimate Javascript can?
If so, I can't tell the difference between that and - say - a CSRF token, which presumably can also be read by "their" Javascript? How does anything work if you have that mentality?
The vast majority of people don't need the scale that is difficult to achieve with regular server-side sessions, and that JWT claims to "solve". They add complexity to solve a problem most people don't have.
> I can't tell the difference between that and - say - a CSRF token, which presumably can also be read by "their" Javascript
CSRF is about e.g. making a user's browser make a form submission that results in a request which is malicious in some way. CSRF Tokens are embedded in each legitimate form to ensure that the submission received came from a form you control.
If the attacker has JavaScript access to your page, CSRF is not your problem, so CSRF tokens can't help you.
Not really talking about sessions, but I think I see what you're saying.
> If the attacker has JavaScript access to your page, CSRF is not your problem, so CSRF tokens can't help you.
I agree. I'm trying to understand what you were saying about whatever your Javascript has access to, their Javascript does as well. Why should this be a criticism of JWT and not CSRF?
Well, mostly a JWT lets you know the user that is signed in.
A server-side session generally does the same thing, but can be used to store larger amounts of data.
> I'm trying to understand what you were saying about whatever your Javascript has access to, their Javascript does as well. Why should this be a criticism of JWT and not CSRF?
They're unrelated attack vectors.
CSRF is about bad actors producing links and/or forms on a different site to your own, that a legitimate user clicks/submits (either through social engineering or some kind of javascript in their page) causing them to make a request to your server. A CSRF token prevents this because it ensures that form submission requests have come from a form hosted on your server.
In the situation where we're worried about what someone else's JavaScript has access to, it means their javascript is already loaded into your page: a vulnerability with poorly escaped user content, a rogue browser extension, a malicious or compromised CDN, etc.
In that situation, CSRF is irrelevant. The CS in CSRF is "Cross Site" - this is no longer cross site, as the script is running in the context of your own page.
So in this situation nothing we do can prevent them from making requests within the current user session.
But what we can prevent them from doing, is stealing a user identifying token: e.g. a session cookie, by marking it HTTP Only, so the JS environment doesn't see it.
JWT's accessed over XHR/etc and stored in local storage are available to any malicious scripts running, meaning they can grab the user's JWT and send it off to their own server, allowing them to make requests as the user.
If you send JWT's as cookies and mark them as HTTP only, you've defeated the "don't send session cookies with every request" goal of JWT, and the cookie will be bigger than most session cookies.
> They're unrelated attack vectors. Good point. I guess I more just meant that what can malicious Javascript do with endpoints protected by JWT that it can't do with endpoints protected another way.
> JWT's accessed over XHR/etc and stored in local storage are available to any malicious scripts running, meaning they can grab the user's JWT and send it off to their own server, allowing them to make requests as the user.
I guess this answers the above question: the difference isn't in what can be executed in the browser, but what can be shipped to a different server to be used in attacks from there.
To mitigate that, then, how's this setup:
1) User's session is maintained in HTTP-only cookies. 2) Browser can use (1) to request a JWT token (and a refresh key) to hit a 3rd-party API endpoint. The token is valid for 5 minutes. 3) Browser can use the refresh key from (2) to request another JWT token.
Does that pretty much bring it up to parity with using cookies everywhere, while keeping the goal of noncentral auth/auth?
It's a pretty good plus, for me: no additional round-trips to the server to grab key user details, which can be put into claims, or check access levels (via roles, permissions, or other types of claim).
This doesn't discount the disadvantages, of course.. I think as with everything it's a case of the right tool for the job. "Depends on the use case".
But, if you're just hiding an additional Delete button on a page based on claims, this comes in handy.
(Edit: in one case, we've used asymmetric keys, i.e. public key so everyone can check integrity. This was a very different use-case to most web apps, though. Overall I'd say if you're carefully checking integrity of something in client-side JS to do something, I think that's probably the wrong approach)
The approach I use is to have a 'use once' refresh token (long timeout) and a security token (short time out) and JTIs to hold a list of logged out/invalid (refresh token used twice) security token IDs.
Here's what I've never understood about this approach: the browser can send many requests at the same time, over the same (HTTP/2) or different (HTTP/1.1) connections. If, say, six requests hit your backend at the same time, all with the same refresh and security tokens, with four more queued up on the user-agent, and the security token is expired, how do you know:
1. that all ten requests are valid,
2. to revoke the security token once,
3. to generate one new security token,
4. to mark the refresh token as used,
5. to generate one new refresh token?
Is it as simple as granting some leeway on how long the tokens can be used after they expire/are revoked? Do you have some way of serializing requests on the client to prevent this from happening? Or do you assign all ten requests the same "batch" ID and tie them together on the backend somehow? Do you do a preflight request to refresh the security token if it's expired?
* Transport: how the session ID or token is shipped between clients and servers (authorization header, cookie headers, or payload).
* Storage: how the session ID or token is stored on clients (cookies, localStorage, sessionStorage, or in memory).
* Statefulness: whether to use a stateless token (with or without a revocation list) or a stateful, server-side session.
* Encryption and/or signing
* Structure/standardization
Examples:
* JWT generates structured, stateless tokens that are signed—and optionally encrypted (JWE)—with the implementor's choice of algorithm. The tokens can be transported and stored by any mechanisms.
* Rack::Session::Cookie (in Ruby) generates unstructured, stateless tokens that are signed—not encrypted—with HMAC. The tokens are transported and stored as cookies.
* Rack::Session::Pool (also in Ruby) maintains an in-memory store of unstructured, stateful sessions. Unsigned and unencrypted session IDs are transported and stored as cookies.
The point being that you can really mix and match. You can even send a session ID in a header and store it as a non-HttpOnly-cookie on the client. Anything goes!
Come on. By all means, criticize flawed implementations containing bugs and security holes, but drop the attention-seeking behavior of screaming loudly about how an entire standard is [insert string of superlatives here related to "worthless" and "broken"]. If you're going to make such incredibly strong claims, your arguments had better be up to snuff.
With good implementations (plenty of which exist), and careful usage (via good coding and design habits), JWT is a fine standard and it can save a solid amount of time when constructing the security portions of a system.
Shouting about how something is 100% flawed and should be cast into the flames may get you plenty of views and outrage cred, but (thankfully) it doesn't say much about the veracity of your analysis.
Checking against a blacklist / some secondary measure may only be required for important actions such as changing password, checking out, etc.
And now there are difficulties around the fact they cannot update an RFC which people will refer to for years.
It's not a vulnerability in one or two libraries - it looks like just about every made the same mistake, which points to something much more broken.
[0] https://auth0.com/blog/critical-vulnerability-in-json-web-en... [1] https://www.ietf.org/mail-archive/web/jose/current/msg05613....
But when is the last time we see any technology successfully prevented people from being silly?
[1]: https://github.com/teotwaki/grace-calendar/blob/develop/app/...
Edit: DYAC.
It's a good thing that cookies have never been used in a bad manner. /sarcasm
You can never stop someone sufficiently motivated to shoot himself in the foot from doing it. But you can make it harder for those who would do it be accident by providing more safety features - in case of security this is usually seen as a good idea (safe defaults etc.)
Do not give people options, do not allow algorithmic flexibility, do not have fallbacks, do not have backward compatibility, do not allow "testing" or "insecure" options, do not have complex state machine behavior.
All of these things are exactly what JWT or other "design by commission" standards like SSL suffer from and they have predictably lead to ongoing, at times unfixable security problems.
Probably I can achieve the same overall system with cryptographically secure session cookies, that are persisted in a database, or other store that is accessible across multiple servers. I guess it would amount to the same thing.
Originally I implemented it because:
* My systems are SPA's. Totally JS dependent from the word go.
* I felt like there would be some advantages to being able to establish certain claims without verification. Say for display purposes prior to server comms (show a list of multiple available sessions for example)...In practise this hasn't really been true. Generally I find in the end I am always checking and verifying anyway - without any huge overhead.
* I've always had a sort of fuzz of uncertainty about Cookies. They always felt a bit out of my hands. Thinking it about rigorously of course, people can switch off JS. They can switch off persistence.
* All my user's local data can be persisted in one place, rather than having to store a reference in the Cookie and then lookup in localStorage. In reality though the code for this is pretty trivial...
So overall while I don't know how right he is, I feel like maybe he has a point. Why not just use cookies?
Maybe it's just because as a JS dev, I want everything to stay within a JS universe...and for some reason Cookies have always felt outside of that to me.
I tried to do the right thing, use HTTP-only cookies set over an HTTPS endpoint only to find that it's stupidly complicated and has a lot of annoying edge cases. Turns out iOS's webviews don't like them, iOS in general doesn't like them to be on api.hostname.com if the app is on app.hostname.com, you can't validate if you are logged in or not without doing a web request (which is annoying as hell if you are trying to keep a "logged in" state in something like a react app), you need to deal with a bunch of stupid flags to get the damn browser to even let them go across domains, and a hell of a lot of other annoyances that I can't remember right now.
We are most likely moving to something like JWTs (stored in localstorage or indexeddb) soon because of these issues.
"app.hostname.tld" doesn't need to actually access the cookies at all, it just needs to make requests to "api.hostname.tld" which sets the cookies and then later validates them.
Unfortunately safari blocks this use case unless you have also been to "api.hostname.tld" directly and there doesn't seen to be any easy way around it (outside of allowing all 3rd party cookies...)
And while iOS safari now handles this (i think they allow *.hostname.tld to use 3rd party cookies for any other subdomains as long as hostname isn't a common provider or something?) it doesn't seem to work consistently for UIWebView or WKWebView hybrid applications. And the "allow 3rd party cookies" setting doesn't seem to apply to the web views either.
Of course the server is going to validate it every request, but it's nicer being able to fail "sooner" on the client side when we know we aren't signed in, or we have never signed in, or our token expired a day ago and we need to re-login, etc...
With HTTPOnly cookies we need to make a request to find any of that out, and when paired with redux and react it's very annoying to have to make a web request to get a small glimpse into what the state really is and try and maintain that in a JS value somewhere AND avoid flashes of incorrect state.
Hell with HTTPOnly cookies you can't even clear it without a web request!
My advice is "use proper framework".
(of course you 'can' but I'm not sure if this is recommended or not.)
The fact that I can't get an app hosted at app.hostname.tld to send a cookie to api.hostname.tld when both the app requesting it allows credentials to be sent in the XHR request and the server is allowing app.hostname.tld to send credentials with the header Access-Control-Allow-Credentials on all platforms is a security feature?
The fact that I can't purge an HTTPOnly cookie in javascript without making a call to an endpoint is a security feature?
The fact that cookies default to JS readable and work across http and HTTPS and you need to make sure to set flags like "Secure" and "HTTPOnly" or you will be open to all kinds of attacks is a security feature?
The fact that cookies are sent on all requests on that domain and preventing browsers from doing that is what brought me down this path in the first place is a security feature?
Yes, there are security features that cookies give you that are extremely useful, however the downsides, bugs, differing implementations, arbitrary defaults, and the need to know the right set of flags and headers to send to make it secure aren't features. Not to mention that you STILL need to do things like CSRF-Protection to actually secure it.
Kinda like Safari throwing exceptions when you're trying to access localstorage in incognito.
So my comment was in the context of having read both of these. In the link here he also strongly argues against storage of JWT in localStorage.
Because in a highly distributed system hitting a database to validate authorization is expensive and causes bottlenecks.
A cookie that requires you to hit the database does not solve that issue. Although if the cookie was signed some way that can be cryptographically verified then great. But then you are essentially re-implementing something you could be doing in a standard way instead.
Don't you have to do something similar to invalidate tokens anyway?
Then, having the 'just-the-right-amount-of-short-expiration-time' for access token helps... maybe? :)
Not exactly..
1. Invalidation lists can be held in memory easier than an entire token database. And if the invalidation list is huge you can distribute a bloom filter across your nodes and use that to check before hitting the database.
2. As another poster pointed out. Bearer JWT tokens are meant to be short lived. If your implementation is ontop of OAuth use a longer lived refresh token to get a new bearer token every so often (say half an hour). So if you are OK with your invalidated tokens being OK for "up to" the expiry (so up to half an hour in this example) you only need to do strong validation on the refresh tokens.
This makes abusing XSS vulnerabilities to get to the token slightly harder.
But JWT takes care of having the expiry signed in the value (in a cookie the expiry is more of a suggestion that a modified client could ignore). Combine that with low expiry JWT tokens and high expiry refresh tokens (subject to more validation) I think it is a clear winner.
The extra overhead on each request of checking these credentials, especially when these requests are hitting the product's database anyway, are often worth the additional security.
I've used JWT in three languages and the API has always sucked, really badly. I always end up with a verbose heap of gunk - and in some cases, like jwt-go, there is not even a complete example of use in the README + docs. mfw. It should not take multiple steps to sign or verify a signature.
[1] https://hueniverse.com/2015/09/19/auth-to-see-the-wizard-or-...