- Lab
- Core Tech

Guided: Security in NestJS
This lab provides a hands-on environment to implement common security practices in a NestJS application, focusing on authentication, authorization, and attack prevention. You will learn how to enforce role-based access control (RBAC) and create custom guards to manage user permissions effectively. Additionally, you will explore security measures to protect against CSRF, XSS, and DDoS attacks in NestJS. This includes implementing throttling, utilizing CSRF tokens in headers, enforcing CORS policies, and securing HTTP responses with CSP headers using Helmet. All these security techniques will be applied within a provided Newsroom NestJS application and its supporting sites, giving you practical experience in securing a real-world project.

Path Info
Table of Contents
-
Challenge
Introduction
Welcome to the Guided: Security in NestJS Lab
In this lab, you will have the environment and instruction provided to teach you how to do the following:
- Authenticate routes with a JSON Web Token (JWT) with a NestJS custom guard
- Authorize routes with role based access control (RBAC) with another NestJS custom guard and decorator
- Store tokens in cookies that are stored on the client's browser
- Throttle and rate limit excessive requests to your NestJS server
- Specify what origins can reach your server with Cross-Origin Resource Sharing (CORS)
- Protect your routes against Cross Site Resource Forgery (CSRF) attacks
- Secure your server even more by including Content Security Policy headers with Helmet
Along the way, this lab will expose you to security threats and principles that will be helpful as you build applications on your own.
Prerequisites
A basic understanding of Typescript and NestJS will be useful to complete this lab, but it is not necessary. Specific knowledge required to complete this lab will be provided.
For the purposes of this lab, any packages or libraries that serve as a dependency for the completion of this lab are already installed for you.
You may find two Terminal windows helpful while you complete this lab. One for running the server and another for running commands to complete lab tasks along the way.
The starting point of this lab includes a NestJS server that is referred to Newsroom. Newsroom includes users who can view news articles (users), users who write news articles (editors), and users who act as administrators (admins). The
users
entity, controller, service, and module have been started for you. Newsroom also lets users see, create, and delete articles. Likeusers
, thearticles
entity, controller, service, and module already have been started for you. The server provided already connects to a PostgreSQL database where users and articles exist.The additional feature modules,
csrf
andcsp-violations
, will be used later in the lab to learn about more security concerns you can prevent when writing a server.--- > Throughout the next steps, you can refer to the
/solution
directory at any point if you need assistance. The/solution
directory contains subdirectories for each of the following steps.Visit Swagger
The NestJS server you will be implementing has documented its routes with Swagger. You can use Swagger throughout this lab to verify that things are working as you would expect them to.
Click this link to open in Swagger in another browser tab: {{localhost:3030}}/api.
Note that using the
start:dev
command will run the application in watch mode, so any changes made in the following steps will automatically compile your changes. -
Challenge
Authentication in NestJS
Overview of Authentication
When developing a server, it is often necessary to restrict access to certain routes, allowing only authenticated users to interact with them. Authentication is the process of verifying a user's identity before granting access to protected resources.
A common approach to authentication is using sessions or JSON Web Tokens (JWTs), which are included in request headers or cookies to maintain user state. In this lab, you will focus on implementing authentication using JWTs.
Once a user is authenticated, additional access controls may be applied based on their permissions or roles. This process, known as authorization, determines what resources a user can access. You will explore authorization in a later step.
Authentication in NestJS
Authentication in a NestJS server can be implemented using the following key components:
-
NestJS's
@nestjs/jwt
library:
This library provides theJwtService
, which is used to generate (sign) and verify JSON Web Tokens (JWTs) for secure authentication. -
Custom authentication guards:
NestJS allows the creation of custom guards (extendingCanActivate
or usingAuthGuard
from@nestjs/passport
) to verify a user's authentication status before granting access to protected routes.
These components work together to ensure that only authenticated users can access specific endpoints within the application.
In the upcoming tasks, you will have the opportunity to implement authentication in the provided NestJS server.
Secure Sensitive Application Data
In the previous task, you used
ConfigService
to fetch the JWT secret—used for signing and verifying JWTs—as well as other configuration values from the.env
file to register theJwtModule
.The
.env
file is intentionally not committed to any code repository. This prevents sensitive application secrets from being exposed to unauthorized users.Following this setup is an important security best practice that you should apply in your own projects to protect confidential information.
NestJS Guards
NestJS guards are used to prevent unauthorized requests from reaching protected routes. Guards implement the
canActivate
method, which determines whether a request is allowed to proceed. To apply a guard to a route, use the@UseGuards
decorator.
Creating a Custom Guard for JWT Authentication
In the upcoming tasks, you will implement the
canActivate
method of a custom guard to verify that a user is authenticated via a JWT token included in the request header.All necessary services for implementing this guard have already been injected into
JwtAuthGuard
via its constructor. These services include:JwtService
– Used to sign and verify JWTsConfigService
– Used to access environment variables from.env
With these services, you will build a guard that ensures only authenticated users can access protected routes.
--- ## JWT Authorization Header
A JWT authorization header follows this format:
"authorization-type authorization-token"
In most cases, the
authorization-type
will be"Bearer"
, meaning the header will look like this:Authorization: Bearer <JWT_TOKEN>
Understanding the JWT Token
- The
authorization-token
is a signed JWT containing encoded information. - While you can decode (unsign) the token to view its payload, you cannot verify its authenticity without the secret key used to sign it.
Security Note
- Never store sensitive data inside the JWT payload, as it can be read by anyone who has the token.
- However, the token's integrity and authenticity can only be verified using the secret key.
See JWT Token in Swagger
Now that the
/auth/login
endpoint has been fully implemented, you can test it using Swagger.Steps to Test:
- Open Swagger UI in your browser.
- Register a new user using the
/users/register
endpoint. - Use the registered credentials to log in via the
/auth/login
endpoint. - If the credentials are correct, you will receive a response like:
{ "access_token": "<JWT>" }
- If the credentials are incorrect, you will receive a
401 - Unauthorized
response.
-
-
Challenge
Apply Authentication Guard to Routes
Apply Authentication Guard to Routes
In the previous step, you implemented the custom guard,
JwtAuthGuard
. In this step, you are going to apply this guard to various routes throughout the NestJS server with the@UseGuards
decorator.--- ## Verify Route Authentication in Swagger
Now that you have routes that require user authentication via a JWT token in the
Authorization
header, you can verify that the guards prevent users from accessing protected routes without being authenticated in Swagger.Click this link to open in another browser tab {{localhost:3030}}/api. If you suspect that Swagger is not reflecting your code changes, you may have to open it another tab (refreshing may not be enough to render code changes).
When you visit Swagger, you will see a page that contains all of the routes. Do the following to authenticate a user and access protected routes.
- Try any protected route and verify you cannot access it as an unauthenticated user.
- Click the down arrow on a route.
- Click the Try it out button.
- Click the Execute button.
- Verify you get a
401 - Unauthorized
response.
- Use the
login
route to login as a user. - Copy the
access_token
in the response body fromlogin
. - Click the Authorize button near the top of the page.
- Paste the
access_token
in the bearer token input value. - Click the Authorize button under the bearer token input value.
- You will notice all of the lock symbols on Swagger go from unlocked to locked.
- Click the Close button.
- Try any protected route and verify you can access it as a logged in user.
If you want to remove that bearer token from the
Authorization
header and log yourself out, follow these steps below:- Click the Authorize button near the top of the page.
- Click the Logout button under the bearer token input value.
- You will notice all of the lock symbols on Swagger go from unlocked to locked.
- Try any protected route and verify you cannot access it as an unauthenticated user.
-
Challenge
Authentication with a JWT in a Cookie
Authentication with a JWT in a Cookie
In previous steps, you implemented authentication using a JWT token passed in the
Authorization
header. This approach requires the client to store the token and include it in the headers for every request.A common alternative, especially for browser-based applications, is to store the JWT in a cookie. This allows the browser to automatically include the token in requests without requiring manual intervention from the client.
What You Will Do in This Step:
- Refactor the
POST /auth/login
endpoint to send theaccess_token
in an HTTP cookie instead of the response body. - Modify
JwtAuthGuard
to retrieve the JWT from the cookie instead of theAuthorization
header.
Why This Matters:
- Improves security by reducing exposure of the JWT in client-side code.
- Enhances usability as the token is managed automatically by the browser.
- Supports session-based authentication in web applications.
This step prepares you for more advanced authentication strategies, including refresh tokens and same-site cookie security.
Cookie Options Explaination
In the previous task, you applied cookie options that looked like:
{ secure: false, httpOnly: true, maxAge: 60 * 60 * 1000, }
Explanation of Each Option
secure
: Whenfalse
, the cookie can be sent over bothhttp
andhttps
. Whentrue
, the cookie is only sent overhttps
.httpOnly
: Prevents client-side JavaScript from accessing the cookie, adding an extra layer of security against Cross Site Scripts (XSS) attacks.maxAge
: Defines how long the cookie will remain valid, measured in milliseconds. In this case, the cookie lasts 1 hour (60 * 60 * 1000
milliseconds).
Additional Option
sameSite
: Controls whether the cookie is sent with cross-site requests."strict"
: Only allows cookies to be sent when navigating within the same site."lax"
: Allows cookies to be sent with GET requests from external sites."none"
: Allows cookies to be sent from any site, but must be used withsecure: true
.
These options help control how cookies behave in terms of security, accessibility, and lifespan. ## Verify Cookie Creation With Swagger
Your routes now use the
access_token
cookie to authenticate users.Since Swagger acts as a client for your NestJS server, you can inspect and verify the cookies sent with requests using your browser’s developer tools.
Follow the steps below to verify that authentication is now handled via cookies:
- Click this link to open Swagger in another browser tab {{localhost:3030}/api}.
- Open Developer Tools in your browser.
- Navigate to the Application tab.
- Locate the Cookies section under Storage.
- If you see a cookie for
access_token
, clear it right-clicking and selecting Clear. - Logout of Swagger Authorization if you are still logged in.
- Login as a user with the
POST /auth/login
endpoint. - Verify that a new
access_token
cookie appears in the Cookies section. - Access a protected route (e.g.,
GET /articles
) without manually adding anAuthorization
header.
Why This Works
- The browser automatically includes the
access_token
cookie in all requests to the NestJS server. - This eliminates the need to manually add an
Authorization
header.
This method helps ensure a seamless authentication experience for browser-based clients
- Refactor the
-
Challenge
Authorization in NestJS with Role Based Access Control (RBAC)
Overview of Authorization
Authorization is a security measure that restricts access to specific routes based on user permissions and roles. If a user attempts to access a resource they are not authorized for, they will receive a
403 - Forbidden
response.
Role Based Access Control (RBAC)
One common approach to authorization is Role-Based Access Control (RBAC). With RBAC, users are assigned roles that determine which routes and resources they can access. This prevents unauthorized users from performing restricted actions.
RBAC in NestJS
In this step, you will add RBAC authorization guards to your NestJS server. To do this, you will:
- Create a custom NestJS decorator called
@Roles
to define allowed roles for a route. - Implement a custom guard called
RolesGuard
to enforce role-based access restrictions. - Apply
RolesGuard
to protected routes. - Use the
@Roles
decorator to specify which roles have access to a given route.
This implementation ensures that only users with the correct permissions can access certain features in your application.
Predefined Users for Testing
These users already exist for you to test Role-Based Access Control (RBAC) and ensure authorization works as expected.
| Username | Password | Role | |-------------|-------------|----------| |
user
|password
|user
| |admin
|password
|admin
| |editor
|password
|admin
|Verify Authorization with Swagger
Several routes are no longer available for a user who is the
'user'
role. Use Swagger to see a403 - Forbidden
request when a user is not authorized to resources based on their role.- Click this link to open Swagger in another browser tab {{localhost:3030}}/api.
- Login as
user
withpassword
. - Try to send a request to
POST /articles
and see you are not authorized to do so.
- Create a custom NestJS decorator called
-
Challenge
Rate Limiting by Throttling
Rate Limiting
Rate limiting is a technique used to control the number of requests a client can make to an API within a specific timeframe. It helps prevent abuse, system overload, and malicious attacks while ensuring fair usage among users.
Rate limiting is crucial to accomplish the following:
- Preventing Distributed Denial of Service (DDoS) Attacks – Protects servers from being overwhelmed by too many requests.
- Stopping Brute Force Attacks – Limits repeated login attempts to prevent password guessing.
- Improving Performance – Prevents excessive load on servers, ensuring consistent response times.
- Saving Costs – Controls resource usage in cloud-based applications where API calls may incur costs.
Rate Limit in NestJS with
@nestjs/throttler
In this step, you will globally configure the
ThrottlerModule
to limit requests globally and for all endpoints on thearticles
controller. You will implement an endpoint-specific rate limit and skip rate limiting for another. ## Verify Throttling with SwaggerIn the upcoming tasks, you will add throttling to the
articles
endpoint.After task 6.4, you should click this link to open in Swagger in another browser tab {{localhost:3030}}/api and verify that endpoints are being throttled or skipped over.
You can do this without logging in, you just need to click
Execute
on the appropriate/arctles
route until you expect to see a429 - Too Many Requests
response. -
Challenge
CSRF Attack Protection
Overview of CSRF
A Cross-site Request Forgery (CSRF) attack tricks a user into unknowingly executing unauthorized actions on a web application where they are authenticated.
Example of How a CSRF Attack Works
1. Victim Logs into a Trusted Website * Example: A user logs into `bank.com` and their session is stored in a cookie. 2. Attacker Tricks the Victim * The attacker sends the victim a malicious link or embedded request (e.g., in an email, forum, or hidden form). * For example, this could be a link like `` * If `bank.com` relies only on cookies for authentication, the browser automatically includes them in the request. 3. The Server Processes the Request * Since the user is still authenticated, the request is executed as if the victim made it. * This could mean the bank transfers money without the user's consent.
CSRF Attack Prevention in NestJS Applications
This step will cover how to prevent CSRF attacks in a NestJS application by implementing CSRF tokens in request headers. You will use a cookie named
'XSRF-Token'
to store thecsurf
session secret, which will be used to validate the provided CSRF token in the request header. The validation process will be handled by the_csurf
middleware, ensuring that all incoming state-changing requests are protected against Cross-site Request Forgery (CSRF) attacks.--- ##
csurf
Middleware ConfigurationWhen configuring
csurf
to use a cookie, the cookie key must be set to'XSRF-Token'
for the middleware to function properly. This middleware initializes at application startup or when a new session connection is established (e.g., opening a new browser window), but it does not run on every request.The
csurf
middleware will only apply to state-changing requests, meaningGET
,HEAD
, andOPTIONS
requests are ignored. This prevents unnecessary CSRF validation on operations that do not modify data.As a result, the
csurf
middleware sends a'XSRF-Token'
cookie to the client, which contains the CSRF session secret. This secret will be used to validate incoming CSRF token headers.
Verify the Creation of the CSRF Cookie
To confirm that the middleware is working correctly:
- Open a new Swagger tab.
- Send a
GET
request. - Check your browser's developer tools (Application → Cookies) to see the
'XSRF-Token'
cookie. - Send a
POST
request and see that the response is403 - Forbidden
because state-changing requests now require aCSRF-Token
header.
--- ## Verify State-Changing Requests Work with CSRF Token Headers
To confirm that the middleware will authorize state-changing requests with a CSRF header:
- Open a new Swagger tab.
- Send a request to
POST /auth/login
. - Verify that you are forbidden to send that request.
- Send a request to
GET /csrf-token
. - Copy the token from the response.
- Open the
POST /auth/login
route. - Paste the token in the
X-CSRF-Token
header in. - Send the request and verify you can login.
Now, the NestJS application is protected from CSRF attacks since no malicious JavaScript can parse the
'XSRF-Token'
cookie to obtain the CSURF session secret, thanks to thehttpOnly: true
option on the cookie. The malicious site is also be unable to send the'XSRF-Token'
cookie because of thesameScript: 'strict'
setting. -
Challenge
CORS
CORS Overview
Cross-origin Resource Sharing (CORS) is a security feature implemented in web browsers that controls how resources on a web server can be requested from a different origin. It helps prevent unauthorized cross-origin requests from malicious websites.
By default, browsers enforce the Same-origin Policy (SOP), which blocks web pages from making requests to a different domain than the one that served the page. However, APIs often need to be accessed by clients hosted on different origins (e.g., a React frontend hosted on
example.com
calling a NestJS API onapi.example.com
).CORS allows controlled access between different origins by adding specific HTTP headers.
## CORS Allows All Origins By DefaultHow CORS Works
1. Client (Browser) Makes a Request to a Different Origin * Example: A frontend hosted on `https://frontend.com` tries to fetch data from `https://api.backend.com`. 2. The Server Responds with CORS Headers * If `api.backend.com` allows `frontend.com`, it responds with headers like: ```http Access-Control-Allow-Origin: https://frontend.com Access-Control-Allow-Methods: GET, POST ``` 3. Browser Enforces CORS Policy * If the CORS headers are present and valid, the request succeeds. * If not, the browser blocks the request, showing a CORS error.By default,
app.enableCors();
setsAccess-Control-Allow-Origin: *
, allowing all domains to make requests to your API without restrictions.While this may be useful for open APIs, it introduces security risks, especially for APIs handling sensitive data.
Potential risks include:
- CSRF attacks (CORS acts as an additional security layer against CSRF).
- Clickjacking attacks, where malicious sites embed your API in iframes.
- Unauthorized use of your API by third parties.
## Test CORS Policy With `curl`Other Defaults Set By
- Allows all origins (`Access-Control-Allow-Origin: *`): - Any domain can make requests to your API.app.enableCors()
-
Allows the following HTTP methods:
GET
POST
PUT
DELETE
PATCH
OPTIONS
-
Allows the following headers:
Content-Type
Accept
Authorization
Origin
User-Agent
-
Exposes these CORS response headers:
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
-
Allows credentials (cookies, authorization headers) only if explicitly set:
- Credentials (
cookies
,Authorization
header) are NOT allowed by default. - To allow them, you need:
- Credentials (
app.enableCors({ origin: 'https://yourfrontend.com', credentials: true, });
Run the
curl
command below in the Curl Commands Terminal:curl -X OPTIONS http://localhost:3030/api -H "Origin: https://good-example.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: Authorization, Content-Type, X-CSRF-Token" -v
If CORS if configured correctly, you should receive the following response:
< HTTP/1.1 204 No Content < Access-Control-Allow-Origin: https://good-example.com < Access-Control-Allow-Methods: GET, POST, DELETE < Access-Control-Allow-Headers: Authorization, Content-Type, X-CSRF-Token < Access-Control-Allow-Credentials: true
This is because this is an allowed origin. You can try the example below to see what happens when you provide an unauthorized origin.
In the same Terminal, run the following commands:
curl -X OPTIONS http://localhost:3030/api -H "Origin: https://bad-example.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: Authorization, Content-Type, X-CSRF-Token" -v
If CORS is setup correctly, you may see the following:
- No
Access-Control-Allow-Origin
header. - Response may be
403 Forbidden
orno CORS headers returned
.
-
Challenge
CSP Headers via Helmet
Content Security Policy (CSP) Headers and Helmet Overview
Content Security Policy (CSP) headers help prevent cross-site scripting (XSS) and data injection attacks by restricting which sources (e.g., scripts, styles, images) can be loaded on a web page. Helmet is a middleware for Node.js/NestJS that sets various security headers, including CSP, to protect against common vulnerabilities like clickjacking, sniffing attacks, and XSS. By configuring Helmet’s CSP settings, you can define which resources are allowed, reducing the risk of malicious script execution.
What is
`'unsafe-inline'` is a Content Security Policy (CSP) directive value that allows the execution of inline or any JavaScript and CSS, which can introduce security risks such as Cross-Site Scripting (XSS) attacks.'unsafe-inline'
?Common CSP Directives
CSP headers consist of various directives that specify which sources are allowed for different types of content.
| Directive | What It Controls | |----------------------|----------------------| |
default-src
| Fallback for all unspecified resource types (scripts, images, fonts, etc.) | |script-src
| Allowed sources for JavaScript | |style-src
| Allowed sources for CSS stylesheets | |img-src
| Allowed sources for images | |font-src
| Allowed sources for fonts | |connect-src
| Allowed sources for AJAX/XHR requests, WebSockets, APIs | |frame-src
| Allowed sources for<iframe>
embeds | |object-src
| Allowed sources for<object>
,<embed>
, and<applet>
| |media-src
| Allowed sources for audio/video content | |worker-src
| Allowed sources for web workers and shared workers | |form-action
| Allowed URLs for form submissions | |base-uri
| Allowed sources for the<base>
tag | |manifest-src
| Allowed sources for web app manifests | |upgrade-insecure-requests
| Automatically upgrades HTTP requests to HTTPS |Testing the CSP Headers
The
GET /csp-violations
endpoint returns an HTML response, which allows you to observe how CSP headers affect the loading of scripts and styles. You can find this HTML file insrc/csp-violations/csp-violations.controller.ts
.Open the following link in a new tab: {{localhost:3030}}/csp-violations.
Once the page is open, go to Developer Tools and navigate to the Console tab. You will see CSP-related errors that indicate which resources are being blocked.
In the upcoming tasks, you will modify the CSP headers by updating the
helmet
configuration and implementing customnonce
middleware.Continue checking the Console in Developer Tools as you make these changes to see how the CSP violations are resolved.
Test the CSP Nonce
Reload {{localhost:3030}}/csp-violations and you should see the website load a script first that you have to dismiss. That means that the nonce was correctly applied and allowed an inline script to run.
-
Challenge
Conclusion
Conclusion
Congratulations on completing all the steps of this lab! You now have the skills to start building secure NestJS applications.
You should feel comfortable identifying various attacks, such as CSRF, DDoS, and XSS, and you should be able to recognize how to mitigate these threats in a NestJS application using:
csurf
for CSRF protection@nestjs/throttler
for rate limiting (DDoS prevention)helmet
for securing HTTP headers (XSS mitigation)
Additionally, you should have a strong understanding of authentication and authorization, including how guards are used in NestJS to enforce security measures.
Security is a vast and evolving field, and while this lab may feel like it only scratches the surface, take a moment to appreciate how much progress you've made. Keep learning, keep building, and keep securing your applications. Great job!
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.