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.

Gerrit Bertier
  • 4,101
  • 2
  • 20
  • 36
Binarynam
  • 144
  • 7
  • 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`? – CrayonViolent Sep 09 '19 at 22:12
  • @Crayon Violent : You mean it HAS to be secure ? – Binarynam Sep 09 '19 at 22:14
  • 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. – CrayonViolent Sep 09 '19 at 22:16
  • Hm i've never heard of this. What would be the solution ? – Binarynam Sep 09 '19 at 22:23
  • 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies)) – CrayonViolent Sep 09 '19 at 22:24
  • 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 :( – Binarynam Sep 09 '19 at 22:37
  • 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. – CrayonViolent Sep 09 '19 at 22:41
  • 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. – Binarynam Sep 09 '19 at 22:47
  • 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. – CrayonViolent Sep 09 '19 at 22:50
  • 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 ... – Binarynam Sep 09 '19 at 23:03
  • `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. – CrayonViolent Sep 09 '19 at 23:17
  • 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. – CrayonViolent Sep 09 '19 at 23:32
  • 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. https://jwt.io/introduction/ and https://auth0.com/learn/json-web-tokens/ for some reading. – CrayonViolent Sep 10 '19 at 00:14

1 Answers1

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 :)

Binarynam
  • 144
  • 7