API with NestJS #117. CORS – Cross-Origin Resource Sharing

NestJS Security

This entry is part 117 of 173 in the API with NestJS

Cross-Origin Resource Sharing (CORS) is a mechanism for disallowing or allowing resources to be requested from another origin. It is built into web browsers and determines whether it is safe to allow a cross-origin request based on HTTP headers. In this article, we explain the CORS mechanism and use it with a NestJS application.

What is an origin?

An origin is part of a URL that consists of the following:

  • protocol (such as HTTP or HTTPS),
  • domain (like google.com),
  • port (such as 443)

Together, the above parts form an origin, such as . Since the default port for HTTPS is 443, we can shorten it to .

Those are examples of URLs that have the same origin:

On the contrary, those are examples of URLs that don’t have the same origin:

Same-origin policy

When we make an HTTP request to a particular website using our browser, it attaches cookies belonging to the website we are requesting. The cookies often hold very sensitive data, such as JWT tokens, that allow us to prove that we used the correct email and password to log in.

If you want to know more about JWT tokens and authentication, check out API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies

Whenever a website requests a resource from a different origin, it is considered a cross-origin request. Web browsers implement a same-origin policy that restricts such requests. The same-origin policy permits a page to access data from another page only if both web pages have the same origin. Thanks to that, it serves as one of the layers of protection that prevent malicious websites or scripts from using our cookies to perform actions on another website we are logged into.

Another useful security mechanism worht knowing is the SameSite flag. If you want to know more, check out Dealing with some CSRF attacks using the SameSite cookies

To test it, let’s go to , open Developer Tools, and make an HTTP request to .

When we do that, we see the following error message:

Access to fetch at ‘https://www.google.com/’ from origin ‘https://www.reddit.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.

Cross-Origin Resource Sharing with NestJS

In the previous parts of this series, we’ve created a lot of different endpoints using NestJS and used them through Postman.

When using Postman, we don’t need to worry about the same-origin policy. However, we usually expect other clients, such as web browsers, to use our API. Let’s create a minimal React application and make a GET request to our API.

I’m using Vite to provide me with a simple React template.

usePostsList.tsx

It is a good idea to put the URL of the API in the environment variables.

When we run our React application on localhost, we can see a Cross-Origin Resource Sharing error.

Access to fetch at ‘http://localhost:3000/posts’ from origin ‘http://localhost:5173’ 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.

The above happens because and have different origins. Therefore, when our React application running at makes a GET request to , it is a Cross-Origin request and is blocked by the browser.

The Access-Control-Allow-Origin header

The error we can see in our React application mentions that the header is missing from the response our NestJS application sends. Because of that, the browser rejects the response.

We can use this header to let the browser know that a particular website can use our API even though it is in another origin. We could do that by writing a simple middleware.

main.ts

While the above works, there are a few things we can improve. First, the URL of our frontend application is likely different in production and a testing environment. Therefore, we should add it to our environment variables in our NestJS application.

.env

app.module.ts

Besides that, NestJS uses the cors library under the hood, and we can use that instead of adding the header manually.

main.ts

Using cookies for authentication

Our NestJS application uses a response header to send a JWT token to the frontend application on a successful log-in request. The frontend is then expected to attach this token through cookies for all requests made to our API.

We need a few tweaks to make the above mechanism work with cross-origin requests. First, we need to use an additional parameter when logging in through our API.

useLogInForm.tsx

Thanks to using , our frontend application stores the cookies received from the backend through the header, even if it was through a cross-origin request.

Unfortunately, we can encounter a problem when using the above approach. Sending a POST request to with the flag causes an error in the console.

Access to fetch at ‘http://localhost:3000/authentication/log-in’ from origin ‘http://localhost:5173’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: The value of the ‘Access-Control-Allow-Credentials’ header in the response is ” which must be ‘true’ when the request’s credentials mode is ‘include’.

In the error message, we can see that we need to send the header in the response from our NestJS application. We can do that by adjusting how we use the method.

main.ts

Thanks to adding the flag on the backend side, we send the response header.

Whenever our frontend application makes a request that requires authentication through cookies, we also need to add the flag. Otherwise, the browser won’t send the cookie with the JWT token to the API.

Preflight requests

It’s crucial to understand that the browser rejects a response from the cross-origin request after it was made, and the browser noticed that the API didn’t respond with the appropriate headers. There is a chance that the malicious cross-origin request already did some harm to our application before the browser rejected it. To mitigate some of this risk, the browsers include the preflight request mechanism.

In certain situations, when making cross-origin requests that include specific HTTP methods or headers, the browser makes a preflight request before making the actual HTTP request. It does not do that when making so-called simple requests.

simple request is a request that uses the GET, HEAD, or POST method and does not involve headers other than:

However, for a request to be considered simple, the Content-Type can have one of the possible values:

Since the POST requests we send from our frontend applications usually contain the type, they don’t qualify as simple requests.

When the browser makes a request that does not qualify as simple, it first makes a preflight request using the OPTIONS HTTP method to check the value of the response headers, such as Access-Control-Allow-Origin. If the header does not contain the necessary value, the browser does not make the actual request.

Summary

In this article, we’ve explained what Cross-Origin Resource Sharing (CORS) is and why the same-origin policy exists in the browser. We configured our NestJS application to work with a React application running on a URL with a different origin and allowed for cross-origin requests. We also learned how to allow the frontend and backend applications to exchange cookies containing the JWT authentication token. Thanks to all this knowledge, we can implement working cross-origin requests when dealing with a NestJS application and a frontend that both work under URLs with different origins.

Series Navigation<< API with NestJS #116. REST API versioningAPI with NestJS #118. Uploading and streaming videos >>
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments