CORS and Fiori/UI5 – Everything you need to know
Everyone who starts developing Fiori/UI5 apps, and doesn’t have a web development background, sooner or later will face Cross-Origin Resource Sharing (CORS) issues and suffer a little bit until wrap their minds around this concept and fully understand it.
Every now and then I need to help a friend/colleague who is getting messages such as:
Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://xxxxxxxxxxx.com’ is therefore not allowed access.
Access to fetch at ‘https://backend.com’ from origin ‘https://frontend.com’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’.
Access to fetch at ‘https://backend.com’ from origin ‘https://frontend.com’ has been blocked by CORS policy: 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.
Access to XMLHttpRequest at ‘https://backend.com’ from origin ‘https://frontend.com’ 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.
Access to XMLHttpRequest at ‘https://backend.com’ from origin ‘https://frontend.com’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
But what the hell is CORS?!? What does that mean?
According to Mozilla Developer Network:
Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. A web application makes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, and port) than its own origin.
Translating to English, it means, by default a web application running on https://frontend.com (one origin) cannot access resources from https://backend.com (different origin), unless the “backend” explicitly allows it.
OK, enough talking. Let’s see some examples…
I’ve identified my OData Service URL from the backend and I’m going to do some tests before writing my Fiori/UI5 app. In this case, the famous NorthWind OData Services from Microsoft: https://services.odata.org/V2/OData/OData.svc
I’m going to test it with JaSON, a tool for testing and debugging web services, (it could be Postman, SoapUI, etc). My OData Service endpoint is the service metadata: https://services.odata.org/V2/OData/OData.svc/$metadata
From browser (direct access):
So far so good. Now, let’s access the same endpoint from my Fiori/UI5 app using an OData Model. My app is running on localhost (127.0.0.1).
//Don't judge me, it's for simplicity's sake const url = 'https://services.odata.org/V2/OData/OData.svc'; const model = new sap.ui.model.odata.v2.ODataModel(url); //Yep, globals...
Oooops… We’ve got an error!
Access to XMLHttpRequest at ‘https://services.odata.org/V2/OData/OData.svc/$metadata’ from origin ‘http://localhost:61264’ has been blocked by CORS policy…
If we try with jQuery Ajax or Fetch API, we’ll get pretty much the same CORS error.
//Yep, no callbacks, don't judge me... const url = 'https://services.odata.org/V2/OData/OData.svc/$metadata'; $.get(url); //jQuery Ajax fetch(url); //Fetch API
It doesn’t matter whether I’m running a Fiori/UI5 app from my local computer, from SAP Cloud Platform or from SAP Fiori Front-end Server On-Premise. CORS error will be the same because I’m accessing resources from another origin, another domain. “Ok, I got it… But, why did it work from JaSON before?” you might ask. It works because JaSON is a developer tool, a plugin installed in your browser. It isn’t a web application being accessed from your browser. JaSON and other dev tools don’t care about Single-origin Policy and CORS. Remember, SOP and CORS are a browser security mechanism, it’s the browser who blocks your ajax/fetch requests.
OK, then what? How can I fix it?
I have some good news and some bad news… The bad news is you CANNOT fix it. I mean, not from your Fiori/UI5 app side, not for good. As aforementioned, the browser expect some very specific HTTP headers from the endpoint being called (another origin). However, you can use some workarounds for testing only. They are not recommended for Production environments.
Now, the good news! You can fix it for real, as long as you have access to the backend and authorization to change some parameters. There are a bunch of HTTP headers to be used for CORS: Access-Control-Allow-WhatDoYouWant? Some options are: Access-Control-Allow-Origin informs the browser which origin has access to the server. Access-Control-Allow-Methods tells the browser which HTTP methods are supported. And so on…
For instance, you could make your SAP Gateway return responses with CORS HTTP headers such as: ‘Access-Control-Allow-Origin’ : ‘http://localhost:61264’. In this example, the HTTP header informs the browser to allow Cross-Origin Resource Sharing requests from http://localhost:61264 only. If you’re feeling brave, you could allow requests from any origin: ‘Access-Control-Allow-Origin’ : ‘*’. You could also tweak few parameters on SAP Web Dispatcher to return these HTTP headers (which is a better idea than handling it on SAP Gateway).
Some other possibilities would be:
Eclipse + proxy (testing only)
If you’re developing your app from Eclipse, you could use a proxy servlet. All you need to do is add proxy to your OData Service URL. It works local only.
//Note the PROXY in the URL const url = 'proxy/https/services.odata.org/V2/OData/OData.svc'; const model = new sap.ui.model.odata.v2.ODataModel(url);
CORS Anywhere (testing only)
It’s a node.js proxy which adds CORS HTTP headers to the proxied requests. You call CORS Anywhere + your endpoint URL and that’s it. It works anywhere 😉
const url = 'https://cors-anywhere.herokuapp.com/https://services.odata.org/V2/OData/OData.svc'; const model = new sap.ui.model.odata.v2.ODataModel(url);
SAP Cloud Platform – Destination
In case you’re deploying your apps to SAP Cloud Platform, it’s even easier. You don’t need to mess around with HTTP headers in the backend, you just need to create a Destination for the OData Service URL and consume the Destination from your Fiori/UI5 app. That’s all, nothing else. It works in a similar way as CORS Anywhere.
If you would like to learn more about the topic, go through the links below. They contain few other concepts related to CORS and more examples. Also, a list of the all HTTP headers used with CORS.
Happy New Year! \o/