CORS – Cross-Origin Resource Sharing
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…
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 https://google.com:443. Since the default port for HTTPS is 443, we can shorten it to https://google.com. Those are examples of URLs that have the same origin: https://google.com https://google.com:443 https://google.com/search?q=wanago.io On the contrary, those are examples of URLs that don’t have the same origin: https://google.com https://google.com:8080 https://www.google.com http://google.com https://reddit.com 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 https://www.reddit.com, open Developer Tools, and make an HTTP request to https://google.com. 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 import { useEffect, useState } from 'react'; interface Post { id: number; title: string; } interface PostsResponse { count: number; items: Post[]; } function usePostsList() { const [posts, setPosts] = useState<Post[]>(); useEffect(() => { fetch('http://localhost:3000/posts') .then((response) => response.json()) .then((data: PostsResponse) => { setPosts(data.items); }) .catch(() => { console.log('Something went wrong when fetching the posts'); }); }, []); return { posts, }; } export default usePostsList; 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 http://localhost:5173 and http://localhost:3000/posts have different origins. Therefore, when our React application running at http://localhost:5173 makes a GET request to http://localhost:3000/posts, 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 Access-Control-Allow-Origin 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 import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { NextFunction, Request, Response } from 'express'; async function bootstrap() { const app = await NestFactory.create(AppModule); // ... app.use(function (request: Request, response: Response, next: NextFunction) { response.setHeader('Access-Control-Allow-Origin', 'http://localhost:5173'); next(); }); await app.listen(3000); } bootstrap();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 FRONTEND_URL=http://localhost:5173 # ... app.module.ts import { Module } from '@nestjs/common'; import { PostsModule } from './posts/posts.module'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi'; import { AuthenticationModule } from './authentication/authentication.module'; import { CategoriesModule } from './categories/categories.module'; import { ProductsModule } from './products/products.module'; @Module({ imports: [ PostsModule, AuthenticationModule, CategoriesModule, ConfigModule.forRoot({ validationSchema: Joi.object({ FRONTEND_URL: Joi.string(), // ... }), }), ProductsModule, ], controllers: [], providers: [], }) export class AppModule {}Besides that, NestJS uses the cors library under the hood, and we can use that instead of adding the header manually. main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ConfigService } from '@nestjs/config'; async function bootstrap() { const app = await NestFactory.create(AppModule); // ... const configService = app.get(ConfigService); const frontendUrl = configService.get('FRONTEND_URL'); if (frontendUrl) { app.enableCors({ origin: configService.get('FRONTEND_URL'), }); } await app.listen(3000); } bootstrap(); Using cookies for authentication Our NestJS application uses a Set-Cookie 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 credentials parameter when logging in through our API. useLogInForm.tsx import { ChangeEvent, FormEvent, useState } from 'react'; function useLogInForm() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleEmailChange = (event: ChangeEvent