• Labs icon Lab
  • Core Tech
Labs

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.

Labs

Path Info

Level
Clock icon Intermediate
Duration
Clock icon 1h 30m
Published
Clock icon Mar 17, 2025

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. 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. Like users, the articles 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 and csp-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.

  2. 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 the JwtService, 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 (extending CanActivate or using AuthGuard 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 the JwtModule.

    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 JWTs
    • ConfigService – 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:

    1. Open Swagger UI in your browser.
    2. Register a new user using the /users/register endpoint.
    3. Use the registered credentials to log in via the /auth/login endpoint.
    4. If the credentials are correct, you will receive a response like: { "access_token": "<JWT>" }
    5. If the credentials are incorrect, you will receive a 401 - Unauthorized response.
  3. 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.

    1. 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.
    2. Use the login route to login as a user.
    3. Copy the access_token in the response body from login.
    4. Click the Authorize button near the top of the page.
    5. Paste the access_token in the bearer token input value.
    6. 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.
    7. Click the Close button.
    8. 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:

    1. Click the Authorize button near the top of the page.
    2. 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.
  4. 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 the access_token in an HTTP cookie instead of the response body.
    • Modify JwtAuthGuard to retrieve the JWT from the cookie instead of the Authorization 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: When false, the cookie can be sent over both http and https. When true, the cookie is only sent over https.
    • 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 with secure: 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:

    1. Click this link to open Swagger in another browser tab {{localhost:3030}/api}.
    2. Open Developer Tools in your browser.
    3. Navigate to the Application tab.
    4. Locate the Cookies section under Storage.
    5. If you see a cookie for access_token, clear it right-clicking and selecting Clear.
    6. Logout of Swagger Authorization if you are still logged in.
    7. Login as a user with the POST /auth/login endpoint.
    8. Verify that a new access_token cookie appears in the Cookies section.
    9. Access a protected route (e.g., GET /articles) without manually adding an Authorization 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


  5. 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:

    1. Create a custom NestJS decorator called @Roles to define allowed roles for a route.
    2. Implement a custom guard called RolesGuard to enforce role-based access restrictions.
    3. Apply RolesGuard to protected routes.
    4. 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 a 403 - Forbidden request when a user is not authorized to resources based on their role.

    1. Click this link to open Swagger in another browser tab {{localhost:3030}}/api.
    2. Login as user with password.
    3. Try to send a request to POST /articles and see you are not authorized to do so.

  6. 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 the articles controller. You will implement an endpoint-specific rate limit and skip rate limiting for another. ## Verify Throttling with Swagger

    In 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 a 429 - Too Many Requests response.

  7. 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 the csurf 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 Configuration

    When 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, meaning GET, HEAD, and OPTIONS 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:

    1. Open a new Swagger tab.
    2. Send a GET request.
    3. Check your browser's developer tools (Application → Cookies) to see the 'XSRF-Token' cookie.
    4. Send a POST request and see that the response is 403 - Forbidden because state-changing requests now require a CSRF-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:

    1. Open a new Swagger tab.
    2. Send a request to POST /auth/login.
    3. Verify that you are forbidden to send that request.
    4. Send a request to GET /csrf-token.
    5. Copy the token from the response.
    6. Open the POST /auth/login route.
    7. Paste the token in the X-CSRF-Token header in.
    8. 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 the httpOnly: true option on the cookie. The malicious site is also be unable to send the 'XSRF-Token' cookie because of the sameScript: 'strict' setting.

  8. 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 on api.example.com).

    CORS allows controlled access between different origins by adding specific HTTP headers.

    How 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.
    ## CORS Allows All Origins By Default

    By default, app.enableCors(); sets Access-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.
    Other Defaults Set By app.enableCors() - Allows all origins (`Access-Control-Allow-Origin: *`): - Any domain can make requests to your API.
    • 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:
    app.enableCors({
      origin: 'https://yourfrontend.com',
      credentials: true,
    });
    
    ## Test CORS Policy With `curl`

    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 or no CORS headers returned.
  9. 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'? `'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.
    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 in src/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 custom nonce 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.


  10. 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!

Jaecee is an associate author at Pluralsight helping to develop Hands-On content. Jaecee's background in Software Development and Data Management and Analysis. Jaecee holds a graduate degree from the University of Utah in Computer Science. She works on new content here at Pluralsight and is constantly learning.

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.