Request.Credentials: Debugging a client-side CORS error

  1. proxy
  2. redirect
  3. cross-origin-resource-sharing
  4. credentials

I have been working on a network interception tool for a while now. At the core of it, it is a simple to use proxy, which; among other things; allows you to easily redirect your requests.

Under the hood, the proxy returns a 307 Temporary Redirect. Unlike returning 301 or 303, a 307 response tells the browser to replay the previously made request to the endpoint that we returned. This is exactly what we want.

But whenever we change the endpoint from which some resource is served for a live website, we have to make sure that the client does not raise any CORS errors. Since CORS is normally configured on the server, we had control on fixing this in our proxy. Initially we resorted to sending the Access-Control-Allow-Origin: * in our response headers.

We never considered work-arounds for any CORS checks that might be applied on the client side, until a user told us about a bug. He saw this error in the console

That was a first!
That was a first!

Request Credentials

Rephrasing the docs here; for any request you can set an attribute called credentials as such.

let myRequest = new Request('my-resource.jpeg');
let myCred = myRequest.credentials; // "same-origin" by default

This request property determines whether request "credentials" (like cookies, http auth headers, auth tokens, etc..) are also sent during a cross-origin request. This is generally done on sites that provide an authenticated or premium service that is hosted on another domain. The other domain could be something like a third party authentication service, a transaction verfication api, a CDN endpoint for premium content, etc.

Credentials can have one of 3 values:

  • omit: Never send or receive cookies.
  • same-origin: Send user credentials if the URL is on the same origin as the calling script. This is the default value.
  • include: Always send user credentials even for cross-origin calls.

This is similar to xhr.withCredentials but with more control on the client to determine which domains to send the credentials to.

The control that request.credentials gives the client is very similar to how same-site attributes work for cookies. Still don't know why they are so similar, but there is this itch in my head that this isn't just a coincidence.

How does Request credentail break CORS... sometimes?

At first glance it would appear that if the request credentials are set to include, CORS should not be an issue. But if you read the screenshot of the bug that led me to write this post, include was the what actually led to the error.

As the error suggests, we cannot send Access-Control-Allow-Origin: * as the response headers. The first response to fixing this was to return the value recieved as Origin in the request as the value for Access-Control-Allow-Origin in the response.

But then, there was this another new error to fix:

This is where it got interesting....
This is where it got interesting....

Sharing credentials in a client-server architecture

Including credentials in the request and response creates a high possibility of a potential CSRF vulnerability.

Hence, if your usecase requires a site to do so, both the server and the client have to specifically declare that they are accepting or refusing credentials in their request/response.

So far we have seen that the client does so by setting the credentials property on the request. For the server to acknowledge that it is ready to accept requests with sensitive information like cookies, it has to also return Access-Control-Allow-Credentials as true in its response response headers.

One caveat here is what if the credentials are sent during a get request. Since there is no pre-flight request (like the OPTIONS sent before a POST), the client has not way of determining whether or not it is safe to send the credentials. This was nicely explained in an StackOverflow comment here that if the server does not respond with Access-Control-Allow-Credentials as true for such a request, the browser does not expose the response data to the client.

This is why we got the previous error.
Well another header added, another issue fixed.... see you on the next bug 😄