Understanding Domains, Cross Origin, Cookies and Services

If you are screaming “HELP, MY SERVER WON’T PERSIST COOKIES TO THE CLIENT!” and are trying to serve cookies from a server that is not in the EXACT SAME DOMAIN as the site being browsed (e.g. if myfunwebsite.com uses api.myfunwebsite.com as a service), then you’ve come to the right place.

Although, instead of hopping directly to the answer, I want to explain what’s going on in the background so that it becomes clear WHY the answer is important.

Understanding Domains

Domains are the root of a URL. Technically a domain can be as short as .com (also known as a public suffix[1]) or as long as this.is.a.really.long.domain.name.org. A subdomain is a domain that is below another domain in the hierarchy (e.g. maps.google.com is a subdomain of google.com or even www.google.com is a subdomain of google.com). A superdomain is a domain that is above another domain in the hierarchy. It’s just a reversed subdomain (e.g. google.com is a superdomain of  maps.google.com).

Domains are huge in creating sandboxes for security within the internet. When Cross Origin calls are made, the security around them is tighter as the domain has technically changed and you are reaching outside your sandbox. So, what exactly constitutes Cross Origin?

Understanding Cross Origin

This one I’ll make short and to the point, unless the domain, port, and protocol (which would actually change the port technically but I digress) are the exact same, requests will be considered Cross Origin.

Given the url: https://this.company.com

Urls subject to Cross Origin:

  • http://this.company.com – Protocol doesn’t match
  • https://company.com – Domain isn’t exact match
  • https://help.this.company.com – Domain isn’t exact match
  • https://this.company.com:8080 – Port changed

Urls NOT subject to Cross Origin:

  • https://this.company.com/another/page/index.html
  • https://this.company.com/another/route

More reading: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy

Clear as glass? Okay well how making a Cross Origin call differ from a regular call?

Understanding CORS

CORS is a way for browsers to help mitigate XSS (Cross-site Scripting). It’s a way for the server to say “I only support calls with these Http Verbs, from this specific origin domain (or all domains), and these specific headers” (NOTE: this is only enforced by the browser, using tools like Postman circumvent CORS).

CORS is important to understand in our use-case because since every call to our rest service is a Cross Origin call, most to all of our REST calls will be subject to CORS.

The well known parts of CORS are:

  • Access-Control-Allow-Origin : designates the expected origin. Can be a specific domain or * which designates all domains (only allows for 1 domain at a time)
  • Access-Control-Allow-Methods : comma separated list of acceptable verbs
  • Access-Control-Allow-Headers : allowed headers

Understanding Cookie Domains

From Mozilla’s Docs:

The Domain and Path directives define the scope of the cookie: what URLs the cookies should be sent to.

Domain specifies allowed hosts to receive the cookie. If unspecified, it defaults to the host of the current document locationexcluding subdomains. If Domainis specified, then subdomains are always included.

For example, if Domain=mozilla.org is set, then cookies are included on subdomains like developer.mozilla.org.

For more reading: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies

The Crux of the Problem

Everything mentioned above is enough to get an API up and running on a different domain than the root, but doesn’t solve our cookie problem.

The crux is that cookies sent/received via CORS are not actually transferred without some extra work. What is this extra work?

The Payoff (Answer)

Here’s the clue that finally made me realize what was going on:

PLEASE KEEP READING, THIS IS NOT THE ONLY THING THAT NEEDS TO BE DONE!

Okay, so Cross Origin calls cannot set cookie values for their own domain without withCredentials! Unfortunately that’s not the only thing that needs to be done…

Here’s everything that needs to be done to get cookies setting properly on Cross Origin calls:

  1. The XHR call needs to have withCredentials enabled
    1. Vanilla Javascript: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
    2. AngularJS: https://stackoverflow.com/questions/13741533/angularjs-withcredentials
    3. Angular 4+ via HttpClient : this.http.post/put/get/etc<type>(url, *body (not here of GET)*, { withCredentials:true })
  2. The server needs to return the header: “Access-Control-Allow-Credentials=true”
  3. The server needs to have an origin specified

 

#3 is the kicker for me we need multiple origins specified, so looks like I’m moving the CORS headers in a layer from IIS layer to the application so that I can set a dynamic allow origin based on where the call is coming from.

Cookie Domain Strategy

Getting cookies to set is only half the battle. You need a cookie domain strategy to make sure that cookies don’t leak to domains that they aren’t supposed to and are accessible to those that are.

 

So if this was a game, there are certain rules we need to follow:

  1. Cookies can only be used on itself or superdomains
  2. Rules that apply to CORS

Simple 1 UI domain to 1 API domain (across multiple test environments):

Notice that none of the cookie domains are subdomains of each other, thus creating a barrier between them.

This setup also makes it easy on the CORS side as there’s 1 domain requesting from the API, so just set the allowed origin to be the UI domain and you’re done.

NOTE: If you are using the service’s domain (e.g. api.dev.example.com from the example) you may not see the cookie set in the browser GUI, but any call that fits the CORS rules we set above will automatically be assigned the cookie as the target domain (e.g. the server domain) matches the cookie domain that was just set.

Adding more UI domains:

When adding more UI domains, all that’s needed is to make sure that the API can handle that domain via CORS (if your hosting tool supports dynamic response headers, otherwise may need custom code). Other than that, the cookie domain is that of the service so it will always be sent with calls to the service.

Adding more API domains:

When adding more API domains, the new API domains must be subdomains of the main domain where cookies are sent. Expect that you can’t set cookies to reach the main domain from here but all the cookies set by the main domain are readable and will be automatically transferred with the call.

 

Wrapping up (TLDR)

The key takeaways here are:

  1. If you have a service that is not the same domain as the UI and need it to set cookies, you need:
    1. CORS setup
    2. XHR requests to have withCredentials enabled
    3. Server to have response header “Access-Control-Allow-Credentials=true”
    4. Server to set a specific “Access-Control-Allow-Origin” (wildcard * will NOT work)
  2. Subdomains are still subject to Cross Origin handling
  3. When creating a cookie domain, make sure to understand the rules and leverage subdomains and unlike domains to create connection and barriers where each are needed