CORS explained to RPG Programmers
The basic concept of CORS
CORS is a mechanism to prevent unauthorized browser scripts from altering data on another machine (cross-origin).
So if a web application on your laptop (origin 1) is trying to access a path (a resource) of a REST service on the IBM i (origin 2) without a CORS implementation, then all browsers1 will block this.
Now you understand the CORS acronym, "Cross-Origin Resource Sharing."
The reason for this block is a network security concept called 'Single Origin Policy.' It means that as a basic concept, we do not trust any machine but our own. All browsers implement this policy, making the World Wide Web relatively secure.
When CORS is implemented, then the browser trusts that you have validated the request.
The browser does this by calling your service twice. First by using the OPTIONS method and then by using the actual method (POST, PUT, DELETE, ..). This OPTIONS action is done automatically by the browser. The CORS errors are thrown if that first OPTIONS call does not finish as expected according to the CORS protocol. That first OPTIONS call is called a preflight request.
1 Accessing the same REST service via programs like CURL or WGET does not enforce CORS rules
CRUD in the land of HTTP
HTTP methods are similar to our database methods (CRUD). Let's take a look:
|RPG database METHOD||HTTP METHOD||CORS ENFORCED|
Aha, so you can also WRITE, UPDATE and DELETE with HTTP? As a concept, YES1! In fact, these POST, PUT, and DELETE requests are the bread and butter of REST services.
The table shows that I have mapped the SETLL method to the OPTIONS method. The concepts are different, but I use them as a comparison anyway.
SETLL is a preparation method to help you READ the correct record. Similarly, the OPTIONS method is a preparation method to allow or disallow the upcoming POST, PUT, or DELETE method in the CORS dance.
So just like the SETLL must go before a READ and a READ must go before an UPDATE, the OPTIONS method is performed before a POST, PUT or DELETE method.
The big difference is that we use SETLL explicitly, but the browser does it automatically before your POST, PUT, or DELETE method. This automatism makes it so obscure; the browser is automatically performing the OPTIONS method (which you have to implement in your REST service).
1 It is up to you how you want to implement the HTTP methods.
The CORS Handshake
The CORS dance is simple once you understand it.
- Before an invasive call is made from a different origin (e.g. POST, PUT, DELETE, ..)
- The Browser calls your REST Service with the OPTIONS method
- You validate the request and set the appropriate HTTP headers
- If the correct HTTP headers are returned then the Browser executes the actual request
Example 1: A REST Service without a CORS Implementation
Below you see an example of a REST service (delete customer) that did not implement the OPTIONS method. I used the CTRL+SHIFT+i option to activate the browser tooling and selected the "Network" tab.
From the image you can see that I tried to excecute the DELETE method to delete a customer and the browser silently executed the OPTIONS method first. Since the REST API did not implement the OPTIONS method, it did not execute the actual DELETE but flagged it as an error.
Example 2: A REST Service with a CORS Implementation
In my RPG Free REST Service, I added the following code:
According to the CORS protocol, I have to return from the OPTIONS method with a '2xx' response and add the following headers:
You can set additional headers, e.g., to inform the browser that it may cache the CORS result. You can read all about it on the CORS specification page.
What about Apache or NGINX
Of course, it is advised to implement the OPTIONS method in your REST Service. By doing so, you have full control over the access to your program.
However, maybe you don't care about CORS because you have dealt with security already, e.g., by using JWT or your own security protocol. In this case you can also let Apache or NGINX handle the CORS dance.
For a good explanation on how to configure Apache for this purpose, see this StackOverflow question answers.