Reverse proxy with redirects is preserving query parameters on 302 response

My site has a _redirects file that contains a redirect to an API, as described in “Proxy to another service.” Below is an example.

/api/* https://api.mydomain.com/api/:splat 200!

Generally, this works as expected. We are integrating a new authentication flow that causes problems. The browser issues a request that gets proxied. That request has query parameters. The server responds with a 302 with the Location header set to a URL with no query parameters. Netlify adds the original query parameters back onto the response. This is not expected and breaks the flow in a very bad way. Below is an example.

  1. Start request GET https://my-app.netlify.app/api/oauthCallback?code=aaaaa&state=abc123
  2. Proxied request GET https://api.mydomain.com/api/oauthCallback?code=aaaaa&state=abc123
  3. Response 302 Location https://my-app.netlify.app
  4. Proxied response 302 Location https://my-app.netlify.app?code=aaaaa&state=abc123

Is there a way to not include query parameters for this case?

Hey there!

This is actually a recent change we made but, of course, for your use case, this isn’t exactly ideal.

We do have a workaround this is detailed here under section “Query String Parameters” which I hope is adequate! In essence, you must provide a standalone query string parameter for the response and we won’t carry forward the parameters from the request.

Thanks for the quick reply @Scott. I’m not sure how I can get what I’d like. I edited the post with numbers on each step and added the original request that gets proxied.

The key is that in step 1, all query parameters get proxied. In 4, only query parameters included in the Location header are proxied. I believe what you are suggesting is for me to create a rule above my /api/* rule. Something like the below.

/api/oauthCallback code=:code state=:state https://api.mydomain.com/api/oauthCallback?code=:code&state=:state 200!

This rule will match routes with exactly this path and query params /api/oauthCallback?code=:code&state=:state. The problem with this approach is that my oauthCallback takes lots of different parameters. I see the docs you linked suggesting to enumerate all the possibilities. That number could be quite large in this case (the route takes a whole different set of optional parameters under error conditions). On top of that, all it takes is the auth service to start calling our /api/oauthCallback with one additional parameter to break our login flow. Does this sound correct or am I missing something?

Hey!

If you don’t want the query string params in the response, you just need to add a dummy query string to the response, such as:

/api/* https://api.mydomain.com/api/:splat?no-query-string 200!

With this, the request should still receive the query string parameters but they’re not returned in the response.

Not including any query string parameters doesn’t work either. I want the query string parameters included in the Location header without needing to explicitly specify the parameters (the params included are dynamic and based on what the UI wants. It’s too hard to enumerate all possibilities upfront).

Sure. So, more specifically:

/api/oauthCallback code=:code state=:state https://api.mydomain.com/api/oauthCallback?no-query-string 200!

Right, so that’s what I brought up here. The problem with that approach is the requirement to enumerate all possible parameters. Since this endpoint takes a lot of different optional parameters, the enumeration would be quite large.

I think I follow along! Please let me know if I’m off the mark with this. Last chance saloon might be:

/api/oauthCallback?* https://api.mydomain.com/api/oauthCallback?no-query-string 200!

All request enumerations should be considered with this, yet won’t be added to the response.

That won’t solve it either :cry:

I need all query parameters to get proxied except in the case I return a 302. When I return a 302, only query parameters specified in the Location header should be included.

Ahhh, okay. In which case, it’ll be two rules at a minimum but, unless there’s any pattern with what’s 302ed then yes, there’ll be a lot of enumeration.

If at all possible, you may want a third parameter which could be useful for distinguishing what should be 302ed and what shouldn’t.