I hate acronyms.
Should have thought of that before getting into IT for a living!
One of the most annoying acronyms, to me, is CORS. It is annoying because it shows up in an error message when you’re trying to make an HTTP request to a URL external to SharePoint.
It may be hard to diagnose if you don’t handle your HTTP request rejections, or if you don’t have your developer tools enabled in your browser, but when you do, you’ll get an error message that looks like this:
workbench.html:1 Access to fetch at 'https://somecoolexternalapi.com/api' from origin
'https://localhost:4321' has been blocked by CORS policy: Response to preflight request doesn't
pass access control check: It does not have HTTP ok status.
This article will explain what CORS is, and how to avoid issues with CORS when making HTTP requests to an external resource.
What is CORS?
NOTE: I’m over-simplifying the explanation and definition of CORS. If you want the real definition, go look at Wikipedia. Just don’t scream at me for being slightly inaccurate, ok? 🙂
CORS stands for Cross-origin resource sharing. It is a way to control how stuff from one web sites (like images, CSS, scripts, and even APIs) is shared with other web sites.
When it isn’t busy ruining your day, CORS can be useful because it allows you to prevent people from pointing to your web site to steal resources from it (while causing extra traffic). Or worse.
It usually works by looking at the domain where the request originates from (e.g.: mytenant.sharepoint.com) and comparing against the domain where the resource sites (e.g.: mycoolapi.com). If the two domains aren’t the same, it is a cross-domain request or — in CORS terms — a cross-origin request.
While you can do some CORS validation on the server-side (that’s another blog), it is usually enforced by your browser. In fact, the CORS standards request that any requests that potentially change data (like an API call) should be pre-validated by your browser before even requesting the resource. That pre-verification is called preflight.
It goes a little something like this:
CLIENT-SIDE COMPONENT: “Hey browser, please call this API from https://somecoolapi.com
BROWSER: “Sure thing. Lemme ask.”. “Hmm, somecoolapi.com is a different domain than mytenant.sharepoint.com, where we are now. I should check first”; calls somecoolapi.com.
WEBSITE: “New domain, who dis?”
BROWSER: “Hey, someone from origin: mytenant.sharepoint.com would like to get access to your API. You can find out all about it in my OPTIONS HTTP headers.”
WEBSITE: “Sure, I don’t see any reasons why you shouldn’t be allowed. Here, let me give you some Access-Control-Allow-Origin headers to confirm I’m ok with it. Just make sure you only GET stuff, no POST or DELETEs, ok?”.
WEBSITE: “Awesome!”; Turns to user, “Good news! somecoolapi.com said they’ll do it!”.
WEBSITE: Makes request. Gets results. Returns results to user.
They lived happily ever after.
The End.
Come to think of it, that’s exactly how I handle phone calls; If call display is blocked, or it is a number I don’t know, I let it go to voicemail. If it is my wife, I answer. She then asks me to buy more Nespresso coffee on the way home. I usually accept the request, because standing between my wife and coffee is like standing between a mother bear and her cub: dangerous.
So, CORS may be annoying, but it is useful.
The problem is that when you make requests to another domain in a SPFx web part using SPHttpClient, you’re making a request from mytenant.sharepoint.com. It usually triggers a CORS error.
To make things worse, when you search for the error, you usually get tons of results on how to change the server settings to prevent the issue. Nothing on how to solve it in your client-side web part.
How to solve CORS issues with SPHttpClient
SPHttpClient, included in , make it easy to make HTTP requests using the current web part’s context.
To access it from your component or service, you need to get the web part’s WebPartContext — I usually pass it into my component’s props, like this:
import { WebPartContext } from "@microsoft/sp-webpart-base";
export interface IMyCustomComponent {
context: WebPartContext;
}
Once you have the WebPartContext you can make the Http request using SPHttpClient, usually something like this:
import { SPHttpClient, SPHttpClientResponse} from '@microsoft/sp-http';
…
/* When ready to make request */
return this.props.context.spHttpClient.get(yourApiUrl, SPHttpClient.configuration.v1)
.then((apiResponse: SPHttpClientResponse) => apiResponse.json()
.then(…) /* Handle the results */
…which is usually when you get the CORS issue.
To avoid the CORS issue, you need to make sure that your request meets the following requirements:
- No custom HTTP headers such as ‘application/xml’ or ‘application/json’
- Request method has to be GET, HEAD, or POST.
- If method is POST, content type should be ‘application/x-www-form-urlencoded’, ‘multipart/form-data’, or ‘text/plain’
However, SPHttpClient tries to be nice and sets a custom OPTIONS HTTP header for you by default.
In order to override the OPTIONS header in your SPHttpClient request, just pass a new/clean IHttpClientOptions parameter, as follows:
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http';
…
/* When ready to make request */
const myOptions: ISPHttpClientOptions = {
headers: new Headers(),
method: "GET",
mode: "cors"
};
return this.props.context.spHttpClient.get(yourApiUrl, SPHttpClient.configuration.v1, myOptions)
.then((apiResponse: SPHttpClientResponse) => apiResponse.json()
.then(…) /* Handle the results */
And that should be it.
Conclusion
CORS can be scary, it can be annoying, but it is a good thing.
You can avoid CORS issues when using SPHttpClient in your SPFx component by passing a ISPHttpClientOptions that doesn’t set custom options.
I only covered how to make GET requests in the code above. You can use a similar approach for HEAD and POST requests.
This approach won’t always work (for example, if the API you’re calling requires custom HTTP headers), but it should solve most other CORS issues.
And if you have any more questions, post a comment, e-mail, or text. Don’t call 🙂
I hope it helps?
8 Comments
Access to script at from origin has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Any idea ?
I use Spfx Angular
import { HttpClient, HttpHeaders } from ‘@angular/common/http’;
getListItems() {
return this.httpClient.get(`${this.BASE_URL}/_api/web/lists/getbytitle(‘eleve’)/items?$select=Id,Title`,httpOptions)
.pipe(
map(response => response.value as ToDo[])
).toPromise();
}
Hi,
My requirement is authenticating the username and password using POST request. Please suggest how that is possible.
error TS2339: Property ‘configuration’ does not exist on type ‘typeof SPHttpClient’.
Thank you Hugo for this great article. So when I try to do this with the following API: https://harvest.greenhouse.io/v1/job_posts?live=true&active=true` I get this error still: “Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource”
The above solution works for others API I try, but not the one I mentioned. I can work around the Authorization error I also get, but I wondered if you know why I got the Access Control error for this specific API.
Documentation on the API: https://developers.greenhouse.io/harvest.html#authentication
Hello,
I’ve tried exactly the same, but still getting CORS issue.
Regards,
Chaitanya
Cross-Origin Read Blocking (CORB) blocked cross-origin response https://myurl/en/news-rss/ with MIME type application/rss+xml. See https://www.chromestatus.com/feature/5629709824032768 for more details.
Hi, is this solution is still working? Didn’t work for me 🙂
Got this error:
OPTIONS net::ERR_CONNECTION_CLOSED
Uncaught (in promise) TypeError: Failed to fetch
Hi, it should work, but when I tried it with the URL you provided above, I got the same error. But I also tried the same address with a browser and Postman and got a 404. The wayback machine seems to have a record of that URL being valid recently (see ) but is it possible that the site is down temporarily?
I also checked “Down for Everyone or Just Me” and it says that URL is down right now ().
The issue may not be in your code!