Javascript won't set httpcookie received in XHR response
Asked Answered
C

1

7

I have a basic SPA (react) <-> API (net core 2.2) setup, with 2 environments: dev and prod (small project). There is an authentication mechanism on the API side that checks the presence of a httponly cookie in every request containing a JWT.

On the dev environment, it works okey-dokey: allowCredentials() is set in the API and withCredentials = true in the react app as well. Both run on a different port of my localhost.

But in a production environment (separate Heroku dynos), it just WON'T set the httponly cookie: I can login using my credentials, the response-headers contain the cookie with the jwt, but every other request i'll make will NOT contain the cookie header at all in request-headers !

I then get a 401 Unauthorized ... error (which is logical). It drives me nuts as I spent hours trying about everything.

My simple authentication XHR (vanilla) call:

var request = new XMLHttpRequest()
request.open('POST', apiAuthenticateUser, true)
request.setRequestHeader('Content-type', 'application/json')
request.withCredentials = true
request.send(postData)

my Startup.cs config in the .net core api :

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
        IdentityModelEventSource.ShowPII = true;
    } else {
        app.UseHsts();
    }
    app.UseHttpsRedirection();

    app.UseCors(
        options => options.WithOrigins(
                "https://localhost:3000",
    "*productionEnvUrl*").AllowAnyMethod().AllowCredentials().AllowAnyHeader()
    );

    app.UseMvc(routes => {
        routes.MapRoute("MainRoute", "api/{controller}/{action}");
    });

    app.UseAuthentication();
}

and thats how i set my httponly cookie containing the jwt in the api controller action response :

Response.Cookies.Append("jwt", jwt, new CookieOptions { HttpOnly = true, Secure = true });

The code is the same on both environments, they just yield different results. In both cases the api sends me the right cookie in authentication response-headers, but in production environment my react app just won't keep it and send it back in other api calls ....

here is the cookie received from the API and that is never sent back from the web app:

Access-Control-Allow-Credentials    :true
Access-Control-Allow-Origin :https://xxxxxxxxxx.com
Connection  :keep-alive
Content-Type    :application/json; charset=utf-8
Date    :Mon, 09 Sep 2019 22:32:54 GMT
Server  :Kestrel
Set-Cookie  :jwt=xxxxxxxx; path=/; secure; samesite=lax; httponly
Transfer-Encoding   :chunked
Vary    :Origin
Via :1.1 vegur

If anyone has any clue i'll be forever grateful.

Course answered 9/9, 2019 at 22:5 Comment(13)
Perhaps your production server has a directive along the lines of Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure - In particular, where it rewrites all cookies to be HttpOnly?Jeanajeanbaptiste
@Crayon Violent : You mean it HAS to be secure ?Course
The Secure flag makes it have to be over https. The HttpOnly makes it to where only the server can access the cookie. So e.g. if your server is initially writing the cookie with this flag, and you attempt to read/write to it client-side, it won't let you.Jeanajeanbaptiste
Hm i've never heard of this. What would be the solution ?Course
Well firstly, make sure it's the problem! The cookie is initially set by the server, yes? Open up your browser dev console and look at the cookies tab (Chrome: F12 > Application > Cookies. Find the entry for your cookie and look at the HttpOnly column to see if it's checked. (p.s. HttpOnly)Jeanajeanbaptiste
Ah, yeah i checked that prior to posting, i just updated my post to include this info. Sadly It didn't give me more clues, everything just seems right YET it won't work :(Course
Okay well in your update, I see the following: Set-Cookie :jwt=xxxxxxxx secure; samesite=lax; httponly so I do see the httponly flag on your cookie. So your client-side (js) code cannot read/write to that cookie.Jeanajeanbaptiste
Hmm, well isnt that the point ? Thats why i use it for jwt storing : its xss safe. The problem is that in my dev environment the httpcokie is sent back in further requests and thats how the authentication works in every api calls, all of this thanks to xhr.withcredentials = true. It just isn't the case in production environment with no obious reason so far.Course
The point of httponly is to only allow the server to read/write to the cookie. This makes your cookie more secure, yes. But the point is that your client-side code cannot read/write to a cookie with this flag, which is what you are trying to do. Are you sure your dev environment includes this flag? Because there's no way your client-side code could be reading/writing to a cookie with this flag.Jeanajeanbaptiste
Hmmm I checked again and just noticed that in dev environment, the httponly cookie becomes a normal cookie. Authentication api call -> returns httponly cookie with jwt -> jwt is automatically set as a normal cookie -> every other api calls will contain this cookie. I'm confused beyond everything : how is httponly cookie xss safe if it is automatically set as a normal cookie on request response ...Course
httponly is a flag you set while setting a cookie, same as any other cookie setting (e.g. secure, domain, path, expire, etc.). The issue isn't with the flag, itself. It sounds like you have an issue with your server code and/or server config in (not) setting it. Your code may even be the same and behaving the same on dev vs. prod and your prod server may separately have a server directive that rewrites all cookies to include it. But this is just one speculation. I don't have full view into all your stuff. But it sounds like you at least have something to investigate now.Jeanajeanbaptiste
Well, rephrase: The issue does look to be an issue concerning httponly flag (not) being set. And that sounds like an issue with your code/server config on dev vs. prod. But in general, again, if you are making use of this flag on your cookie, you can't use client-side code to read/write to the cookie, which sounds like is a problem for you. So your options are to a) refactor your code to not have to read/write the cookie client-side, or b) Do not make use of httponly flag on the cookie.Jeanajeanbaptiste
Well I think I'm getting a bit out of my depth here with what you're actually doing with it, but my general understanding is you don't/shouldn't be writing your jwt to a cookie in the first place. I believe you should be sending it as an Authorization header, using the Bearer schema. jwt.io/introduction and auth0.com/learn/json-web-tokens for some reading.Jeanajeanbaptiste
C
2

Well turns out i got a lot of things wrong :

  • Everything was fine with my web app code.
  • It worked in development environment because everything was running on localhost (just different ports), which is authorized by the samesite=lax (default value) cookie policy.
  • In order to make it cross-domain you simply have to specify samesite=none, then it works. It creates some potential vulnerabilities since you lose some control over your cookies, but unless you set up some reverse-proxy/api gateway mechanism this is the only way around as far as cookies (httponly included) are concerned.
  • I got over-excited about the rule "jwt should only be stored in httponly cookies" : it has vulnerabilities too, and you still have to make the rest of your application xss (and other techniques) safe. You can safely store a jwt in the local storage if you take all the precautions needed.

Thanks to @Crayon Violent for his time :)

Course answered 10/9, 2019 at 1:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.