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.
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.
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!
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.
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).
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.
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.
> 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.
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.
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.
> 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.
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.
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.