Skip to Content
FrontendSecurityImplement CSRF Protection

Implement CSRF Protection

Introduction

Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they’re authenticated.


Structure of a JWT-CSRF token

{ "exp": 1739422155, // The expiration time of the token "iss": "http://demo.dev.inventory.phpr.link/fr/product", // The issuer of the token "aud": "http://demo.dev.inventory.phpr.link/fr/product", // The audience of the token "jti": "be6f7cbc-8a20-43ce-944d-3927e63c62ac", // Unique identifier for the token "value": "cAqJ8hfM-8GiIeE9a374bb_0PbqkrvZw9qKE", // CSRF token "sub": "phpreaction_csrf_token", // The subject of the token "iat": 1739378955 // The time the token was issued }

How to protect your app from CSRF

In api folder

Create a folder csrf

Create a get csrf token file : /api/csrf/get-csrf-token/index.ts

This will be used to get the CSRF token.

Import the getCsrfToken function from the utils bundle, which will handle the fetch process of the CSRF token

import { getCsrfToken } from "@phpcreation/frontend-utils-react-nextjs-bundle/api-functions"; export async function GET(request: Request) { return getCsrfToken(request); }

It will :

  • Generate the CSRF token
  • Verify (and decode) the CSRF token
  • Return the decoded CSRF token
import { generateCsrfToken, verifyCsrfJwt } from "utils/functions"; import { getFromCookie } from "utils/helpers"; export default function getCsrfToken(request: Request): Response { const response = new Response(); let token = getFromCookie(request, "csrfToken"); if (!token) { token = generateCsrfToken(response, request.headers.get("referer") || ""); } else { try { verifyCsrfJwt(token); } catch (error) { if (error.name === "TokenExpiredError") { token = generateCsrfToken( response, request.headers.get("referer") || "" ); } } } let decodedCsrfJwt = verifyCsrfJwt(token); return new Response(JSON.stringify(decodedCsrfJwt), { headers: response.headers, }); }

Create a generate jwt-csrf token file : /api/csrf/generate-jwt/index.ts

Import the generateJwt function from the utils bundle, which will handle the generate process of the CSRF token

import { generateCsrfJwt } from "@phpcreation/frontend-utils-react-nextjs-bundle/api-functions"; export async function GET(request: Request) { return generateCsrfJwt(request); }

This will be used to generate a JWT CSRF token for each pages so that the CSRF token is unique for each page. It will :

  • Get the URL of the requester using :
    • The referer header
    • The pathName query parameter provided in the URL
  • Get the CSRF token from the cookie
  • Verify (and decode) the CSRF token
  • Generate a new JWT CSRF token with the actual URL so that the CSRF token is unique for each page
  • Return the new JWT CSRF token
import { generateJWT, verifyCsrfJwt } from "utils/functions"; import { getFromCookie } from "utils/helpers"; export default function generateJwt(request: Request): Response { const url = new URL(request.url); const finalUrl = request.headers.get("referer") || url.origin + url.searchParams.get("pathName"); const csrfToken = getFromCookie(request, "csrfToken"); const decodedCsrf = verifyCsrfJwt(csrfToken); if (typeof decodedCsrf !== "string") { const newJWT = generateJWT(decodedCsrf.value, finalUrl); return new Response(JSON.stringify(newJWT)); } return new Response(JSON.stringify({ error: "Error decoding JWT" })); }

In layout

Import the CSRFProvider from the contexts in utils bundle

// CSRF import { CSRFProvider } from "@phpcreation/frontend-utils-react-nextjs-bundle/contexts";

Englobe your app with the CSRFProvider

<CSRFProvider>{children}</CSRFProvider>

In pages where you need to use CSRF (Where there is API calls)

Get the CSRF token from the context

const { csrfToken } = useCSRF();

Add it to the payload of every CallAPI

await CallAPI( "POST", tenant, CallAPIURL.projectTasks.get + "/" + currentTaskId + "/task_manager/punch", "", JSON.stringify({ csrf: csrfJwt, description: description, userId: user?.id, }) );

How is CSRFContext implemented

Variables

const [csrf, setCsrf] = useState<string>(""); const [csrfJwt, setCsrfJwt] = useState<string>(""); const pathName = usePathname();
  • csrf : Will be the CSRF token will be verified during API calls
  • csrfJwt : Will be the JWT of the CSRF token and will change on each page
  • pathName : The pathName of the current page

Functions

fetchNewCSRF

This function will be used to get a new JWT CSRF token for the current page

async function fetchNewCSRF() { const response = await fetch("/api/csrf/generate-jwt?pathName=" + pathName); const data = await response.json(); setCsrfJwt(data); }

fetchCSRF

This function will be the initial fetch, it’ll get the CSRF token at the beginning

async function fetchCSRF() { const response = await fetch("/api/csrf/get-csrf-token"); const data = await response.json(); setCsrf(data.value); }

useEffects

useEffect(() => { fetchCSRF(); }, []); useEffect(() => { if (pathName && csrf) { fetchNewCSRF(); } }, [pathName, csrf]);
Last updated on