Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server schemes that expect you to read cookies #3

Open
rgrempel opened this issue Jul 13, 2016 · 24 comments
Open

Server schemes that expect you to read cookies #3

rgrempel opened this issue Jul 13, 2016 · 24 comments

Comments

@rgrempel
Copy link

I think it is probably true that if you control both the client and the server, there is always a way to set things up so that the client code does not have to be aware of cookies.

However, there are some servers which expect the client code to be able to read a cookie. Here is a link to one such scheme, in which the client must read a cookie and send it back in an HTTP header, in order to prove something that helps avoid CSRF attacks.

http://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-cookie

Now, this is not, even in Spring, the only way to structure CSRF protection. So, if you control the server, you can make things work without cookies.

There are, however, cases in which you do not control the server.

Of course, whether this is a compelling use case is a separate question, and I do not have anything interesting to say about that.

@cassiemeharry
Copy link

This isn't unique to Spring. The official way to use the CSRF protection in AJAX requests for Django is to get the csrftoken cookie and send it as an extra header.

As a reminder, CSRF protection is designed to prove that you're submitting a form from the same domain as the destination. This prevents evil.com from submitting a from to mybank.com/sendpayment if I happen to be logged into the latter.

With Django at least, we can't use the "unique identifier" sessionid cookie because that's (rightfully) set HttpOnly and is sent regardless of whether or not we started on the destination site. CSRF protection requires that an additional piece of authenticated information is sent that varies from page-load to page-load, and a cookie is the best way to get that for making AJAX requests.

@rgrempel: I'm curious about what alternative methods that Spring uses to achieve CSRF protection of AJAX requests besides cookies.

@rgrempel
Copy link
Author

If you scroll up a bit from the target of the link I gave, you'll see a variation on the scheme, in which the CSRF token is provided in the "meta" headers of the HTML document. So, you can read it from there, and provide that back as the proof with the AJAX request.

But, of course, using a cookie to send the token from the server to the client is convenient -- and, in any event, if you don't control the server, and it does send a cookie, then you're going to have to be able to read the cookie on the client.

@justgage
Copy link

If the only use case I'd to get around legacy APIs would it make sense to just say "use ports"? Then again it would stink to have to make special

@justgage
Copy link

Arrangements just because your framework on the server is restricted. It would be nice to have elm be a full replacement for js. I think if we did allow this API we should have it in big letters "USE LOCAL STORAGE if at all possible" then discuss the shortcomings of the cookie api

@fahrradflucht
Copy link

But doing it like Spring or Django is actually the most secure way. If you just use local storage you are vulnerable to XSS attacks that steal the JWT or what have you. If you just use a cookie you are obviously vulnerable to CSRF. But using a cookie that is set by the server and is httponly and proving that you are able to read a different CSRF token that is not httponly protects you against CSRF and makes XSS harder because one needs the constantly changing CSRF token. This is not 100% secure either because a XSS can still make valid requests but it can't steal JWTs for later use.

I'm not for or against this library but I don't think that it is valid to call such implementations "legacy APIs"

@clintonb
Copy link

clintonb commented Dec 4, 2016

The lack of cookie support in Elm is a non-starter for me. Form submission is one problem. I also have a need to share data across services on different subdomains. Cookies are the simplest solution to that problem. I was hoping to experiment with Elm; but, the cost of re-architecting the data sharing solution hardly seems worth the benefit of using Elm.

Note that I simply need to read cookies via JS. Can we at least get that capability?

@mltsy
Copy link

mltsy commented Feb 9, 2017

Interesting conversation! @clintonb So, when you talk about the need to "share data across services on different subdomains" are you talking about basically having an Elm app make API requests to some external domain and receive cookie headers in the response, and be able to incorporate that data into the model and/or pass them to other domains? What kind of data would your "services on different subdomains" be sending in cookie headers that you'd need to read?

@clintonb
Copy link

clintonb commented Feb 9, 2017

Full disclosure: I have moved past Elm, so this is not a priority for me.

@mltsy at edX, our learner system (courses.edx.org) writes an enrollment hash to a cookie. This hash is used by our marketing site (www.edx.org) to determine if we need to refresh enrollment information from an API. I was investigating using Elm for a marketing site prototype, so I literally just wanted to read a value from the cookie.

While there are libraries for doing so, implementing local storage across subdomains is not a simple task...and involves adding more third-party dependencies to our front-end stack.

@justgage
Copy link

justgage commented Feb 9, 2017

Auth0 often stores JWTs in cookies as another +1 to this issue. Here's a quote describing the issue

Stormpath recommends that you store your JWT in cookies for web applications, because of the additional security they provide, and the simplicity of protecting against CSRF with modern web frameworks. HTML5 Web Storage is vulnerable to XSS, has a larger attack surface area, and can impact all application users on a successful attack.

~ https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage

However, I'm perfectly happy to write a rather simple port to handle this. Not at all a dealbreaker

@mltsy
Copy link

mltsy commented Feb 10, 2017

@justgage, If you're storing your JWT in a cookie, the Elm app shouldn't need to know anything about it, right? (The browser will automatically send the JWT with every relevant request if it's in a cookie). Unless, of course, you're also storing app data in the cookie, but if the data is needed by the app, why not send that data in the response body rather than a cookie header? Is there any reason to store data needed by the app in a JWT (which is supposed to be only for authentication/authorization to resources on the server)?

@clintonb I see, so basically caching of data so you don't have to make the same (or similar) API requests from both domains in the same browser. Interesting... It does seem like, ideally, localStorage would be the best place for that data (i.e. via elm's persistent-cache), since it is designed to act as a local cache. As for the sub-domain issue, according to the localStorage Spec you should be able to share localStorage across subdomains by simply setting the orgins of the pages to the same parent domain using document.domain

All that said, I think being able to read cookies is still probably a nice convenience for interacting with legacy servers without having to go through a port. Aside from that though, I'm surprised to agree with the sentiment that it's not technically necessary!

@justgage
Copy link

The JWT just asserts things. If you don't put the user_id in there you don't know they are the user they say they are. You could send up a "what's my user_id" request and just have the server pull it out but that seems kind of silly if it's already client side in the form of a cookie. It's really not a hard port to write and can usually be done with flags so I'm not too concerned.

@mltsy
Copy link

mltsy commented Feb 13, 2017

I suppose if you want to get the current user ID in your app it could make sense to pull it out of the cookie. But I can't think of a situation where an Elm app would need to know the current user ID, without also needing to make a request to the server for additional related information anyway. For instance, if you're looking at a user list, and want to highlight or offer different actions for the current user, you would need to know the user ID. But in that case, you're making a request to get the list of users anyway, so that list could include a flag on the current user (based on the JWT cookie the server received asserting the current user), so the actual relevant app data all comes form the server, and the Elm app still doesn't need to be able to access the cookie in that type of situation.

The only time it would really make sense to access the cookie is if it contained actual cached app data (i.e. an entire user profile, or permission authorization scheme for the current user, or something), but none of those cases, as far as I can tell, are actually good use cases for cookies, after the advent of localStorage. Maybe Auth0 does store that kind of data in the JWT though? I was assuming it would just store a user ID, expiration, and maybe a couple other auth-relevant fields.

Or perhaps there's another application model I'm not thinking about where the Elm app requests data using some authentication scheme other than the current user's session credentials, and then needs to be able to read the current user's credentials to decide what part of that data is relevant to them. In THAT case, I could see that it might be the Elm app's responsibility to deal with auth information... but that seems like a pretty insecure model for an app, considering all the auth logic there would be running on and editable by the client :)

@Superpat
Copy link

So is there any solution for someone using an elm client to connect to a haskell-servant server that uses servant-auth for authentication? It requires that the X-XSRF-TOKEN be set as a header. But the actual xsrf token is sent inside a cookie.

@mltsy
Copy link

mltsy commented Oct 25, 2017

Now that's an interesting case! I don't know haskell, so have never used haskell-servant or servant-auth, but that's an odd authentication scheme! I took a look at the README here: https://github.com/haskell-servant/servant-auth/blob/master/README.md

It seems like servant-auth also accepts cookies as an authentication method, right? In that case, you could just let the browser handle auth, as outlined above (with it's standard handling of cookies by convention). But if you don't have control over the servant-auth configuration, this is indeed a case, as described in the OP where you don't control both server and client, and so therefore Elm needs to handle the cookie data.

The only option currently (excluding abandoned native module packages) for reading cookies is to use ports (or a Native module, but... ports), and read the cookie with javascript.

@Superpat
Copy link

Superpat commented Oct 25, 2017

While there is a pr for alternative xsrf handling for cookies, the cookie based authentication requires the xsrf header to be set as well, I believe this choice is based on an olasp recommendation. I think I'll use the ports method for now.

While I understand the idea to limit cookie use, it would be nice to have read-only access to them through elm. I believe that would fix most problems without encouraging bad practices too much.

@mltsy
Copy link

mltsy commented Oct 25, 2017

Oh, I missed that it was an XSRF token (I'm used to the Rails abbreviation, CSRF). Another possibility for handling something like that if it's something that only gets sent once on page-load (rather than in a cookie that is returned with an XHR request after initializing the app) is to read the token in Javascript and pass it to Elm when you initialize the app, using flags

@Superpat
Copy link

No it's received after sending a login command that makes an api call and sends a message back to the app with a success or error. Now what I've done is create two ports, one that sends a boolean to javascript to signal a login success and another to receive the xsrf token from javascript. It compiles fine, but I'm stuck on how to subscribe to the ports in the javascript. See halfzebra/create-elm-app#194

@Superpat
Copy link

Everything works fine now. I'm really satisfied with how easy to use ports are. Though maybe any javascript exceptions in ports should be automatically caught? Forcing ports to use a Results type.

@Superpat
Copy link

Superpat commented Nov 8, 2017

So further experimentation with my haskell servant-auth is that it sends back a new xsrf token after every single request and will refuse requests if that token is not added to the headers. Now I initially fixed this by having every http command send a command to my requestXsrf port and having the GetXsrf message update the model with the new xsrf token. But now I'm trying to do http requests in tasks but I'm facing a big challenge, I'd have to update the xsrf token between every task. Is there a way to communicate with ports with tasks?

@mltsy
Copy link

mltsy commented Nov 8, 2017

Wow - it sends back a new XSRF only as a cookie on every request, and expects you to send it back in the body? That is one interesting auth scheme! 😆
I think your only choice here (whether elm could read cookies or not) is to perform each task independently, updating the model with the new cookie values in between.

@Superpat
Copy link

Superpat commented Nov 8, 2017

I finally fixed it after taking a look at this: https://github.com/mebassett/servant-db-elm-auth-example

You do have to create a Native Module, but I think this is an acceptable sacrifice. I turned the sendCsrfToken cmd in that example into a task and now I can simple use Task.map2 with my merge function and my two requests wrapped in the sendCsrfToken task.

@mltsy
Copy link

mltsy commented Nov 8, 2017

Oh, nice - great example of how to get around this kind of problem using a Native Module! :)

@saurabhnanda
Copy link

Just chiming in with a use-case -- https://github.com/haskell-servant/servant-auth#xsrf-and-the-frontend

@asvanberg
Copy link

Now that native modules have been killed off and we can no longer work around the lack of reading cookies are there any plans to add this support to Elm so that the above use cases can be handled?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants