Pitfalls Of OAuth2 and CORS

Sam Gruse
Mesh
Published in
4 min readNov 9, 2017

Our in-house product [Standup] allows users to get automated progress reports for their engineering teams. To deliver that granular reporting, we have to support OAuth2 for all of their tools (Github, JIRA, Slack, Etc).

Our backend is built with [Typescript] on Node (Express). We leverage the popular authentication framework [Passport]to handle all of our authentication and authorization flows. Implementing an OAuth flow was new to me, but I assumed it would work the following way:

  1. The user selects a service to OAuth with from our WebApp which sends a request to our sever.
  2. Our server responds with a redirect URL that will take the user to the OAuth page of the selected service e.g Jira.
  3. The user logs into their Jira account which produces a verifier token.
  4. This verifier token is then sent back to our server and is exchanged for an access token which completes the OAuth flow.

With all my modules in place, I thought I was good to go, only to be hit with the following error in the browser when I tried to test it all out:

Error: No Access-Control-Allow-Origin on the requested resource.

What is this error and why are we getting it?

In order to keep data safe between the client and browser, the browser takes note of every piece of code that it downloads from a server, keeping track of the server’s origin. An origin is a combination of URI, host-name and port number, taken from the request / response headers.

These pieces work together to create a fingerprint for the server that the browser is receiving information from. The browser will allow any code downloaded from the server to make subsequent requests back to the server, so long as both the code making the request, and the server receiving the request; derive from the same origin. In other words, with default configuration in place, code downloaded from one server can only request information from that same server. This behavior is a result of the Same Origin Policy which is used in all browsers in order to keep data stored in the browser secure.

There are a number of mechanisms that offer a work around to the Same Origin Policy, one of which is called CORS — Cross Origin Resource Sharing. In it’s simplest form, the CORS spec offers a set of headers that can be used to allow or restrict communication between a browser and server. I knew that the error above had to do with a missing CORS header but didn’t understand how to format my request correctly, especially because our server was already configured to use CORS.

Chrome Developer Tools is a great resource for debugging all things web, and it can give you insight into all requests from/to the browser. When I tried to analyze my redirect issue with the tool, I could see the request being issued by my WebApp, but it was never getting a response from the server. This was especially confusing since my server logs showed the request being completed…

This was extremely difficult to debug but after some digging I realized this was due to the way Chrome handles 302 status codes. During the initial leg of an OAuth flow, you are redirected to a third party’s site for authentication. My server middleware was responding to the WebApp with a 302, and Chrome was intercepting this request. What the error indicates is that the requested resource my server was asking my browser to redirect to, the( Jira URL), didn’t have the appropriate response headers in place.

To navigate this issue while maintaining CORS, there are two major gotchas to watch out for. First, due to CORS restrictions, the server must respond with a status code other than a 302. This restriction is in place to prevent the browser from automatically making an HTTP request before the WebApp has a chance to accept the redirect payload. Second, once the WebApp receives the response containing the redirect URL, the browser must be redirected in a way that isn’t subject to the CORS policy. This will avoid cross origin filtering due to the Same Origin Policy.

To do this I first had to make a change in the redirect code located in the passport middleware. Passport assumes that correct status code to send in this scenario is a 302 so changing the redirect code from:

To:

Now that we’re sending a 200 status code, the browser will not think it has to do the automatic redirect, and the WebApp will now receive the response from the server containing the redirect URL. The next step is to simply redirect the WebApp to the URL contained in the server response using window.location.assign(url), window.location.href(url), or the one I went with, window.location = (url).

When the WebApp reads this line of code, the browser will perform an internal redirect which isn’t subject to the CORS restrictions. This means that there won’t be a check for the Access-Control-Allow-Header and the OAuth flow will complete as planned.

The above solution works great and will allow the WebApp to see the response from the server containing the redirect URL payload. The only issue with it is having to make changes to the Passport library which can be a hassle. If you don’t mind the redirect happening automatically and don’t want to deal with changing the Passport library, there is another way to have this redirect take place without running into CORS issues. This solution involves wrapping the initial request to the server for the redirect URL in an anchor tag.

<a href="http://request.to.server.for.redirect.url/"></a>

The value of the ‘href’ attribute in the anchor tag is used to direct the browser to either an external or internal resource. In this implementation, we are making an external request to the server, and because the hyperlink is already pointing to an external resource, the response containing the redirect (which also points to an external resource, i.e Jira) is allowed. Both of these solutions will work to avoid CORS errors and successfully complete the OAuth flow.

--

--