3

I am pretty sure that the answer to my question is no, but I have been have a hard time finding an answer through official documentation or other posts here. Here is simple use case for some context:

  1. Python backend web application (api.domain.com)
  2. Frontend JavaScript SPA (app.domain.com)
  3. post requests to api.domain.com/api/auth/login/ made from app.domain.com using axios with the correct username and password return a response with an access JWT token in the body and the response sets a refresh cookie with an HttpOnly flag [should fail, since I believe that the cookie cannot be set on app.domain.com from an API request to api.domain.com? -- this is my question]
  4. the access token is stored in memory and passed with each API request
  5. requests made to api.domain.com/api/auth/refresh/ are sent on a schedule to refresh the short-lived access token.

I typically host the frontend app and backend app on the same subdomain (app.domain.com) and do path-based routing with something like CloudFront or nginx, and this works well. For example, all requests starting with /api/* are sent to the backend, and all other requests are sent to the frontend app. Trying to use a separate subdomain for the API seems to fail no matter what options I use for setting the cookie on the server.

Can someone help me confirm that it is in fact not possible to set an HttpOnly cookie on a subdomain like app.domain.com from an API request hosted on api.domain.com? It would be great if anyone can also help me find where this could possibly be found in official documentation.

Searching for set httpOnly cookie across subdomains, I haven't found anything directly relevant. I also didn't find anything in these resources that directly answers my question:

https://owasp.org/www-community/HttpOnly

https://docs.microsoft.com/en-us/previous-versions//ms533046(v=vs.85)?redirectedfrom=MSDN

1

2 Answers 2

2

I had the exact same problem and it turned out there were three things going wrong:

  • The JS code doing the request using Axios, an HTTP library which has a bug around cookie handling: https://github.com/axios/axios/issues/2149.

    The fix was to use plain fetch and specify credentials: "include" in the options. Without this option, cookies are not included in cross-domain requests.

  • The server was not including the Access-Control-Allow-Credentials header in the response, which has to be set to true on both the login endpoint (for the Set-Cookie to be accepted and stored by the browser) and the refresh-session endpoint (for the cookie to be sent back, here the OPTIONS response is the relevant piece).

  • I set Same-Site: Strict in the cookie attributes (and prefixed the Cookie name with __Host-) assuming this would allow the cookie to only be sent back to the same domain but it resulted in the cookie not being stored at all. Same-Site has to be None for the cookie to work in cross-origin requests at all and the __Host- thing also seems to block the cookie from being stored when it comes from a different host. Omitting the Domain cookie attribute should be enough to stop the cookie from being sent to other domains.

0

That has been my experience and is, from what I understand, an actual cross domain attack :). If you want to use HTTP only cookies for auth you need both services to be under the same domain. With the help of a friend I set this up with nginx locally (nginx.conf below in case others need it). Both the front end app and back end app are served out of the same domain so that the browser will manage the cookies correctly. The last location (location /sockjs-node) is to allow webpack to hot reload on save for development since this is a react app front end.

As an aside, you might want to re-think your token pattern. The one I'm following in Flask off of the documentation is an HTTPOnly Cookie with the JWT token and then a csrf_token as a cookie that's readable by JS. You send the csrf_token with each request as a header and it's also encoded in the JWT. Kind of a two factor auth for the JWT in addition to it's signature from the generator server.

You would want to replace hal with your local user.

# user  nginx;
user hal staff;
worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include mime.types;
    default_type application/octet-stream;
    #access_log /var/log/nginx/access.log;
    sendfile on;
    keepalive_timeout  65;
    server {
        client_max_body_size 15m;
        listen 80;
        server_name local.earthdawn.com;
        charset utf-8;
        # error_log /var/log/nginx/error.log debug;
        index index.html;
        autoindex on;
        # root "/Users/hal/Dev"; #this is for a root dir that you want to pull static files from
        # added by Erik 28-02-2020 - caching handled by the CDN
        add_header "Cache-Control" "max-age=0, no-cache, no-store, must-revalidate";
        add_header "Pragma" "no-cache";
        add_header "Expires" "-1";
       
        # frontend app forwarding
        location / {
            proxy_set_header X-Real-IP       $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://localhost:3000;
        }
        # api forwarding
        location /api {
            proxy_set_header X-Real-IP       $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://localhost:5000;
        }
        # allows hot reloading for react app
        location /sockjs-node {
            proxy_pass http://localhost:3000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
        }
    }
}
5
  • 1
    what exactly are you saying is "an actual cross domain attack"? Do you know where I can find documentation about the fact that you confirmed? (If you want to use HTTP only cookies for auth you need both services to be under the same domain) Oct 6, 2021 at 14:12
  • Sorry for the delay in replying.
    – Will
    Oct 10, 2021 at 15:10
  • With my current side project I had the same issue you were having of wanting two separate servers and to use HTTP only cookies as part of the auth flow. The flow worked in Insomnia as it emulates the domain of the endpoint it hits by default. Looking at MDN's docs it looks like you can set a cookie to a specific domain and it may simply be a limit in either the library I'm using or my knowledge ( more likely ). The domain defaults to the setter's domain.
    – Will
    Oct 10, 2021 at 15:26
  • As to the cross sight forgery, my limited understanding, is that part of the forgery is emulating the domain that the user is using in a hopes to bypass the security check that a back end service is performing (ie. all requests from http://good.to.go/ are OK). The goal of an httpOnly cookie is that it can't be manipulated on the client side. If you were able to access cookies across a domain, this would put a whole in that built in bit of security because you would be broadening access to that cookie. Your serve would lose the confidence that it was only coming from the intended recipient.
    – Will
    Oct 10, 2021 at 15:31
  • If you look at the MDN docs above under Domain, you seem to be able to set your cookies as accessible under subdomains. "For example, if you set Domain=mozilla.org, cookies are available on subdomains like developer.mozilla.org." However, they also mention that this will open you up to a session fixation attack. "This mechanism can be abused in a session fixation attack. See session fixation for primary mitigation methods." So it feels like you'd want to definitely dig deeper into that.
    – Will
    Oct 10, 2021 at 15:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.