ServiceStack CORS Issue with React

We’re trying to set up ServiceStack and CORS within our test environment. Currently there is no IIS security set up (anonymous). When trying to connect from the client (React) the request is rejected with:
“Access to fetch at ‘https://xxx?format=json’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.”

We’ve tried the below to set up ServiceStack but we’re unsure if this is a server setup issue or a client setup issue. The ServiceStack project is hosted on a test server where the client is just running a localhost:3000 React project. The React project has been able to connect with a Rails API call. Is there an example of how to connect to a React project? The GET and Post work, but the Put and Delete do not.

Here's my request:

const https = require("https");

function deleteStaffMember(e) {
  console.log(e.currentTarget.name)
  try {
    const res = fetch(`https://xxx/${e.currentTarget.name}?format=json`, {
      method: 'DELETE',
      agent: new https.Agent({
        rejectUnauthorized: false
      }),
      mode: 'cors',
      credentials: 'omit'  --also tried 'include'
    })
  } catch(err) {
    console.log(err)
  }
}

Here are the AppHost service stack attempts, from current to previous attempts:

public override void Configure(Container container)
{
//Permit modern browsers (e.g. Firefox) to allow sending of any HTTP Method
SetConfig(new HostConfig
{
GlobalResponseHeaders = {
{ “Access-Control-Allow-Origin”, “*” },
{ “Access-Control-Allow-Methods”, “GET, POST, PUT, DELETE, OPTIONS” },
{ “Access-Control-Allow-Headers”, “Content-Type” },
},
});
}


**earlier:**

Plugins.Add(new CorsFeature(
allowOriginWhitelist: *,
allowCredentials: false,
allowedHeaders: *));


**even earlier:**

Plugins.Add(new CorsFeature(
allowOriginWhitelist: new[] { “http://localhost”,“http://localhost:3000” },
allowCredentials: true,
allowedHeaders: “Content-Type, Allow, Authorization, X-Args”));


**original:**

Plugins.Add(new CorsFeature());


Here's my DELETE (I assume this doesn't get as much because it's the pre-flight request):

DELETE /NDBServiceStack/staff/hierarchy/1315 Origin: [http://localhost:3000](http://localhost:3000/) Referer: http://localhost:3000/index Accept:  */*  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15

I’m assuming this post is the same as this question.

Whenever posting HTTP interoperability issues like this you should be looking at the full Request/Response Headers showing the problematic requests for the best chance to be able to identify what the issue is.

I’ve provided the best answer I can without a working repro that uses well known CorsFeature configuration. I’ll need a Minimal Repro Example to be able to investigate further.

I don’t have a repo yet but we were experimenting with curl commands and the servicestack cors scenarios. When we use the CorsFeature we never see the Access-Control-Allow_Origin header at all with curl requests. When we use SetConfig(new HostConfig set GlobalResponseHeaders we get the Access-Control-Allow-Origin when we curl a delete but not if we curl an options (where options is how the browser would be attempting the delete scenario with the pre-flight). Any thoughts into why we’re getting this behavior?

Make sure you’re using the CorsFeature configuration from my answer then post the exact curl requests you’re making along with their raw HTTP response.

Ok. We reset the CorsFeature to exactly what you had proposed. I’m sending a Delete curl and a Options curl with verbose info.

curl “https://xxx.xxx.org/NDBServiceStack/staff/hierarchy/1?format=json” -X DELETE -v

DELETE /NDBServiceStack/staff/hierarchy/1?format=json HTTP/1.1
Host: xxx.xxx.org
User-Agent: curl/7.55.1
Accept: /

< HTTP/1.1 204 No Content
< Cache-Control: private
< Content-Length: 0
< Vary: Accept
< Server: Microsoft-IIS/10.0
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ServiceStack/5.80 Net45/Windows
< Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
< Access-Control-Allow-Headers: Content-Type, Allow, Authorization, X-Args
< Access-Control-Allow-Credentials: true
< X-Powered-By: ASP.NET
< Date: Fri, 06 Mar 2020 16:52:43 GMT
<

C:\Users\linaar>curl “https://xxx.xxx.org/NDBServiceStack/staff/hierarchy/1?format=json” -X OPTIONS -v

OPTIONS /NDBServiceStack/staff/hierarchy/1?format=json HTTP/1.1
Host: xxx.xxx.org
User-Agent: curl/7.55.1
Accept: /

< HTTP/1.1 200 OK
< Allow: OPTIONS, TRACE, GET, HEAD, POST
< Server: Microsoft-IIS/10.0
< Public: OPTIONS, TRACE, GET, HEAD, POST
< X-Powered-By: ASP.NET
< Date: Fri, 06 Mar 2020 16:53:36 GMT
< Content-Length: 0

Also, do we need the IIS CORS module or is the ServiceStack plugin supposed to handle all CORS needs?

I’ve removed curls verbose debug logging messages which isn’t helpful, only the headers are which looks correct. I’m assuming the DELETE Request returned a 204 No Content Response (and 0 Content-Length) because the Service does not return a response.

You’re not seeing the Access-Control-Allow-Origin response because your request doesn’t include the Origin HTTP Request Header.

Based on your comments, we’ve setup the ServiceStack plug-in as you’ve described. I’ve added the Origin header as described but we now get the below error.

Request Headers (Running IIS Anonymous currently):

headers: {‘Content-Type’:‘application/json’},
crossDomain: true,
method: ‘DELETE’,
agent: new https.Agent({
rejectUnauthorized: false
}),
mode: ‘cors’,
Origin: ‘hxxp://localhost:3000’,
credentials: ‘omit’

Access to fetch at ‘hxxps://xxx.xxx.org/NDBServiceStack/staff/hierarchy/1?format=json’ from origin ‘hxxp://localhost:3000’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

Please post the raw HTTP Request and Response Headers. If the browser sends the correct Origin header ServiceStack should emit the Access-Control-Allow-Origin that the error suggests isn’t being returned. You’ll need to inspect the raw HTTP Headers to see what’s really being sent.

So, how can I tackle that? From the browser tools I see some basic request headers but there are no response headers. Is this because its a pre-flight scenario? All I get is the below. How else can I send you the raw info via the browser/server negotiations?

Content-Type: application/json
Referer: hxxp://localhost:3000/
Sec-Fetch-Dest: empty
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36

Post screenshots of the Web Inspector Headers pages

This is all that displays for headers for this given call.

image

Not sure what that incomplete Request is, it may be a previously cached request? Can you try disable caching and do a full refresh to see if it makes a difference.

That request doesn’t include the Origin header, can you post the headers of your first Request/Response that includes the Origin header?

I had a developer provide me with his responses. Below is a get that works fine of course but you can see the header responses as expected. He gets the same lack of header information when attempting the delete. It seems to be something with the pre-flight.

async function deleteStaffMember(id) {
  try {
    const res = await fetch(`https://xxx.xxx.org/NDBServiceStack/staff/hierarchy/${id}?format=json`, {
      method: 'DELETE',
      agent: new https.Agent({
        rejectUnauthorized: false
      }),
      credentials: 'omit',
      mode: 'cors',
      crossDomain: true,
      origin: 'http://localhost:3000'
    })  
  } catch(err) {
    console.log(err)
  }
}

The first Request looks how it’s supposed to, it looks like it worked so you’re successfully able to make a CORS request.

The browser is supposed to be adding the Origin Request Header, I don’t know what your agent crossDomain or origin properties are supposed to do as they don’t look like part of fetch official API, are you using using a custom fetch polyfill? Also as per my original StackOverflow answer I’d recommend using credentials:'include' which is required if you want to send user credentials along with the request

Not being able to see the Request Headers will inhibit in being able to identify the issue, if you aren’t able to view the raw HTTP Headers in WebInspector you can try Fiddler, this should be a direct link by-passing their spam/marketing pages:
https://www.telerik.com/download/fiddler/fiddler4

But are you sure this is even a CORS issue? As you’re using IIS this might just be its WebDAV module interfering with your DELETE Request, you can try disabling WebDAV.

Otherwise since GET/POST works you can simulate a DELETE request with the X-Http-Method-Override Request Header.

I checked and WebDav is not installed. If I use fiddler for the delete and the options I get the below. It all appears to be accurate.

Both of these are successful requests/responses, what’s the issue with them?

I guess the only issue at this point is that it works via Fiddler but not via the fetch in React.

Use Fiddler to capture the fetch request from React.

Can you test if simulating the DELETE request over POST (from my previous comment) works?