Cross origin request explained

If You are a developer getting started or have played with frontend as well as backend development for some time now then you must have faced issues with CORS a lot of the time. and I guess by now you must have known how to solve them. If you have not then follow along and you will know the reason why it is occuring and how to resolve it. If you already know how to solve it but don't understand the theory and reason behind what you do in order to solve it then you are in the right place. If you are a backend developer then you might have also heard a lot of your frontend guys ask you to enable CORS.
For a frontend dev, the typical error you get is this.
Access to fetch at 'http://localhost:3000/' from origin 'http://localhost:3001' 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.
In the above snippet, localhost is just a local domain and can be any domain depending on where you are working.

Now let's suppose you have build an API whatever language you use doesn't matter. for the purpose of this exercise, you will find nodejs snippets for mocking of our API response.
Assuming an endpoint in nodejs is to support a simple get and post request 
require('dotenv').config()
const express = require('express')
const bodyParser = require('body-parser')
const fetch = require('node-fetch')
const path = require('path')
const app = express()
const port = 3000
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
// example API call to naza api for image of the day
app.get('/', async (req, res) => {
try {
let image = await fetch(`https://api.nasa.gov/planetary/apod?api_key=${process.env.API_KEY}`)
.then(res => res.json())
res.send({ image })
} catch (err) {
console.log('error:', err);
}
})
// API call for the persons
app.post('/persons', async (req, res) => {
let matricule = req.body.matricule
let person = {
name: "Harisu",
gender: "Male",
Tel: '+237675955931',
email: 'fanyuiharisu@gmail.com'
}
res.send(person)
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

If we run this code and locally then we expect to get the response object of the image of the day from Naza at the / endpoint by making a get request. however, if you use a frontend technology or and application that runs on the browser, you will get the typical CORS error explained above.

The example below uses react to make a fetch to the backend.
import React, { useEffect, useState } from 'react';
import './App.css';
function App() {
const [innitial, setInitial] = useState()
const [person, setPerson] = useState()
useEffect(() => {
async function FetchData() {
const resp = await fetch('http://localhost:3000')
const result = await resp.json()
setInitial(result)
}
FetchData()
}, [])
const submit = async () => {
const response = await fetch('http://localhost:3000/persons', {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ matricule: "FE!$A)(4" })
})
const result = await response.json();
setPerson(result)
}
return (
<div className="App">
<button onClick={submit}> Post Request</button>
</div>
);
}
export default App;

Unfortunately what we get it this
What does it mean and why can't we fetch data from our API?. The answer to this question will be given.

Note that if you make this same request with postman you will get a response back and everything will work normally.
This is the same request with postman

Why can't we fetch data from our API?

The answer is what is known as.
  1. Same-origin request policy:  It is enabled by default on all browsers
  2. Need to configure cross-origin request policy on the API

Same Origin Request policy:

What we mean here is that When our browser download a javascript file, it remembers from where(domain) it downloaded the file. So for the case above, It knows that it downloaded the code from http://localhost:3001 Later on in the browser when this javascript code tries to make a request to another domain which is not http://localhost:3001 In our case http://localhost:3000, the request is blocked.
The diagram below represents what happens
.


To be more specific,
The javascript code does not directly sends the request to the API instead the browser sits in between the code and the API, which means the browser is the one that performs the actual request it receives the request it needs to send from the js code then add some additional headers(origin) to it and forwards it to the API. now when the API returns or response, the response as well is not directly available to the code it is again intercepted by the browser and this time around the browser looks for a particular header and if it notices that the request went to a different domain, it again checks for that particular header and if it's absent it will block the response data. As such in other to combat this, we the developers need to update the API to respond with that particular header which the browser will be looking for(Access-Control-Allow-Origin.

New Updated Diagram.



Once our API response with a header having the access-control-allow-origin then the browser can now allow the response to reach the JS code depending on the value of the access-control-allow-origin

Access-Control-Allow-Origin Values

Access control allow origin header is allowed to have any of the two possible values 
  1. wild card "*" for all domain ie any js file downloaded from anywhere on the net can make request to this API and get response.
  2. `hostname: port`  limit only a particular domain to access the API
Note: This CORS issue we are discussing is basically affecting only the browser. Tools like postman are not affected.
For our example express code, it's as easy as adding the line below to the request.

res.header('Access-Control-Allow-Origin', "*")
Now when you perform the same get request you get the response with the header as follows
in postman and in our react app the CORS error is gone Also not that the line above should
the first line in the function that processing the get request.



Everything seems to be working just fine with the GET method. Now if you try and click on the button for our example code which is supposed to make a post request you still get CORS error even after adding the Access control allow origin line to the post method. Why is this, We will be answering it below.
You might think we already configured CORS but the thing is some requests need more level of configuration example post and delete method. When you make a post request or a delete request the browser intercepts and makes what is known as a preflight request. This request by default uses the options method specifying the intention of our request Access-control-request-method. ie it says our code is about to make a post request to the specified endpoint and in return, the browser is expecting the API to respond with another type of header this time around its expecting to receive the Access-Control-Allow-Origin header and the Access-Control-Allow-Methods as well as the Access-Control-Allow-Headers.
When the browser receives the above two headers from the preflight request, It then goes ahead to make the actual post request and the API response with the data alongside the Access-Control-Allow-Origin:* header are returned.

To solve the above-identified issue, we should add an option method that will respond to the preflight request typically it looks like the following the below code is nodejs express but the same 
idea works for other languages.

app.options("/*", function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
res.send(200);
});

The above code will always respond to the preflight request and then the browser will be able to now make the actual request. After adding the above code we now have a successful request on our react app with the following

This concludes our post about CORS Explained.

Watch out.

This article gives you just an explanation of how and why things happen the way they do happen when it comes to CORS and not just fixing the CORS errors with a package.
In a production environment, you will want to use a CORS package and do the configuration using the package but you could also with the knowledge gained from this post write your own middleware to handle CORS in your application.

Comments

Post a Comment

Popular posts from this blog

Algorithm Design A Call For Concern To Software Engineers

Week Twelve

A health wise peep into the life of a home based developer