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