Dealing with CORS and authentication

Dealing with CORS and authentication

The frustration with CORS is common to all developers, especially when dealing with user authentication, but it doesn't have to be that way, with there being several ways of fixing these issues!

Note that in this article, client & server and frontend & backend mean the same thing and will be used interchangeably

What is CORS?

Cors which stands for Cross-Origin Resource Sharing is a mechanism that allows the server to tell the browser which origins other than its own are allowed to make requests to it or load resources.

Browsers implement the CORS mechanism to prevent cross-origin HTTP requests which can make websites vulnerable to CSRF attacks.

So how do we connect to our server from the client?

Well, one way would be to host both the backend and frontend on the same origin (eg: https://example.com & https://server.example.com ).

But in a lot of scenarios, especially while developing the application the server and client are likely to be on different origins. Even if both of them are hosted on localhost they will be on different ports and any request from the client will be recognized as a cross-origin request.

An easy way to fix this problem is to use a proxy in the client. For example in React we can add a proxy to the package.json file like so:

"proxy": "URL to the server"

But this is only applicable in development and the real problem starts when we move into deploying the app on different origins.

Using the CORS package

The CORS package in node.js lets us define the origins which are allowed to make requests. It does this using a middleware function that applies that rule to every route.

Here's an example using express and the cors package

// Import packages
const cors = require('cors')
const express = require('express')
const app = express()

// Add a list of origins you would allow
const whitelist = ['http://example1.com', 'http://example2.com']

// Define the options to the middleware function
const corsOptions = {
  origin: function (origin, callback) {
    if (whitelist.indexOf(origin) !== -1 || !origin) {
      callback(null, true)
    } else {
      callback(new Error('Not allowed by CORS'))
    },
   credentials: true
  }

// Add the cors middleware
app.use(cors(corsOptions))

When dealing with authentication it is important to add the credentials: true property so that we can send back the cookies stored in the client to the server

There are packages similar to this for other languages like flask-cors for python.

The sameSite Attribute

It is to be noted that when sending cookies from the server to the client which is on a different origin a few properties have to be set.

We need to set the sameSite attribute to 'none' because we are sending the cookies to a different origin and that makes it necessary to set the secure attribute to true

res.cookie('cookieName', cookieValue, { 
    maxAge: 1000, // lifespan of a cookie
    httpOnly: true, // prevents client from using the cookies
    sameSite: 'none', 
    secure: true 
});

Configuring the client

Now that the backend is configured it is now necessary that we set up the frontend to send credentials such as cookies to the server on every request

In this example, we send a POST request to our server to log in and set credentials: 'include' to get back cookies

const sendRequest = async () => {
 const res = await fetch('{serverURL}/login', {
    method: 'POST',
    body: JSON.stringify({ email, password }),
    headers: { "Content-type": "application/json" },
    credentials: 'include',
})
}

And there you have it, now you can say goodbye to all those annoying CORS errors!

References