From 232ce57f92a590d61bbc01c4d6927289af4b1d3c Mon Sep 17 00:00:00 2001 From: Andres Morey Date: Mon, 15 Apr 2024 18:30:47 +0300 Subject: [PATCH] wip --- README.md | 199 ++++++++++++++++++++++-------------------------------- 1 file changed, 79 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index 621ca00..e1f405f 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,25 @@ # Edge-CSRF -Edge-CSRF is CSRF protection for [Next.js](https://nextjs.org/) that runs in middleware (edge runtime). +Edge-CSRF is a CSRF protection library that runs on the [edge runtime](https://edge-runtime.vercel.app/) (as well as the node runtime). -This library uses the cookie strategy from [expressjs/csurf](https://github.com/expressjs/csurf) and the crypto logic from [pillarjs/csrf](https://github.com/pillarjs/csrf) except it only uses Next.js edge runtime dependencies so it can be used in [Next.js middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware). +This library implements the [signed double submit cookie pattern](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#signed-double-submit-cookie-recommended) using the crypto logic from [pillarjs/csrf](https://github.com/pillarjs/csrf) except it only uses edge runtime dependencies so it can be used in both node environments and in edge functions (e.g. [Vercel Edge Functions](https://vercel.com/docs/functions/runtimes/edge-runtime), [Cloudflare Page Functions](https://developers.cloudflare.com/pages/functions/)). It comes with easy-to-use integrations for Next.js and SvelteKit plus a lower-level API for more custom implementations. ## Features -- Supports app-router and pages-router Next.js 13 and Next.js 14 -- Runs in edge runtime -- Implements cookie strategy from [expressjs/csurf](https://github.com/expressjs/csurf) and the crypto logic from [pillarjs/csrf](https://github.com/pillarjs/csrf) +- Runs on both node and edge runtimes +- Includes Next.js middleware integration (Next.js 13, Next.js 14) +- Includes SvelteKit hooks integration - Gets token from HTTP request header (`X-CSRF-Token`) or from request body field (`csrf_token`) - Handles form-urlencoded, multipart/form-data or json-encoded HTTP request bodies +- Supports Server Actions via form and non-form submission - Customizable cookie options - TypeScript definitions included **Note: There's an issue with Next.js middleware in v13.3.X and v13.4.X that prevents edge-csrf from working properly with the pages-router in a dev environment (https://github.com/vercel/next.js/issues/48083, https://github.com/vercel/next.js/issues/48546)** -## Quickstart +## Install -To use Edge-CSRF, first add it as a dependency to your app: +To use Edge-CSRF, just add it as a dependency to your app: ```console npm install edge-csrf @@ -28,35 +29,23 @@ pnpm add edge-csrf yarn add edge-csrf ``` -Next, create a middleware file (`middleware.ts`) for your project and add the Edge-CSRF middleware: +## Next.js + +To integrate Edge-CSRF with [Next.js](https://nextjs.org/), create a middleware file (`middleware.ts`) for your project and add the Edge-CSRF middleware: ```typescript // middleware.ts -import csrf from 'edge-csrf'; -import { NextResponse } from 'next/server'; -import type { NextRequest } from 'next/server'; +import { createMiddleware } from 'edge-csrf/nextjs'; -// initalize protection function -const csrfProtect = csrf({ +// initalize csrf protection middleware +const csrfMiddleware = createMiddleware({ cookie: { secure: process.env.NODE_ENV === 'production', }, }); -export async function middleware(request: NextRequest) { - const response = NextResponse.next(); - - // csrf protection - const csrfError = await csrfProtect(request, response); - - // check result - if (csrfError) { - return new NextResponse('invalid csrf token', { status: 403 }); - } - - return response; -} +export const middleware = csrfMiddleware; ``` Now, all HTTP submission requests (e.g. POST, PUT, DELETE, PATCH) will be rejected if they do not include a valid CSRF token. To add the CSRF token to your forms, you can fetch it from the `X-CSRF-Token` HTTP response header server-side or client-side. For example: @@ -136,115 +125,81 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) } ``` -## Examples - -See more examples in the [examples](examples) directory in this repository: - -| Next.js Version | Router | Implementation | -| --------------- | ------ | --------------------------------------------------------------------------------------- | -| 13 | app | [HTML form](examples/next13-approuter-html-submission) | -| 13 | app | [JavaScript (dynamic)](examples/next13-approuter-js-submission-dynamic) | -| 13 | app | [JavaScript (static)](examples/next13-approuter-js-submission-static) | -| 13 | pages | [HTML form](examples/next13-pagesrouter-html-submmission) | -| 14 | app | [HTML form](examples/next14-approuter-html-submission) | -| 14 | app | [JavaScript (dynamic)](examples/next14-approuter-js-submission-dynamic) | -| 14 | app | [JavaScript (static)](examples/next14-approuter-js-submission-static) | -| 14 | app | [Sentry](examples/next14-approuter-sentry) | -| 14 | app | [Server action (form)](examples/next14-approuter-server-action-form-submission) | -| 14 | app | [Server action (non-form)](examples/next14-approuter-server-action-non-form-submission) | -| 14 | pages | [HTML form](examples/next14-pagesrouter-html-submission) | - -## Server Actions - -Edge-CSRF supports server actions with both form and non-form submission in the latest version of Next.js (14). - -### Form Submission +## Sveltekit -With server actions that get executed via form submission, you can add the CSRF token as a hidden field to the form ([see example](examples/next14-approuter-server-action-form-submission)): +To integrate Edge-CSRF with [SvelteKit](https://kit.svelte.dev/), create a server-side hooks file (`hooks.server.ts`) for your project and add the Edge-CSRF handle: -```tsx -import { revalidatePath } from 'next/cache'; -import { headers } from 'next/headers'; -import { redirect } from 'next/navigation'; +```typescript +// src/hooks.server.ts -export default function Page() { - const csrfToken = headers().get('X-CSRF-Token') || 'missing'; +import { createHandle } from 'edge-csrf/sveltekit'; - async function myAction(formData: FormData) { - 'use server'; - console.log('passed csrf validation'); - revalidatePath('/'); - redirect('/'); - } +// initalize csrf protection handle +const csrfHandle = createHandle({ + cookie: { + secure: process.env.NODE_ENV === 'production', + }, +}); - return ( -
- Server Action with Form Submission Example: - - - -
- ); -} +export const handle = csrfHandle; ``` -### Non-Form Submission +Now, all HTTP submission requests (e.g. POST, PUT, DELETE, PATCH) will be rejected if they do not include a valid CSRF token. To add the CSRF token to your forms, you can fetch it from the event's `locals` data object server-side. For example: -With server actions that get executed by JavaScript calls (non-form), you can pass the CSRF token as the first argument to the function ([see example](examples/next14-approuter-server-action-non-form-submission)): - -```tsx -// lib/actions.ts -'use server'; - -export async function exampleFn(csrfToken: string, data: { key1: string; key2: string; }) { - console.log(data); +```typescript +// src/routes/+page.server.ts +export async function load({ locals }) { + return { + csrfToken: locals.csrfToken, + }; } +export const actions = { + default: async () => { + return { success: true }; + }, +}; ``` -```tsx -// app/page.tsx -'use client'; - -import { exampleFn } from '../lib/actions'; - -export default function Page() { - const handleClick = async () => { - const csrfResp = await fetch('/csrf-token'); - const { csrfToken } = await csrfResp.json(); - - const data = { - key1: 'val1', - key2: 'val2', - }; - - // use token as first argument to server action - await exampleFn(csrfToken, data); - }; - - return ( - <> -

Server Action with Non-Form Submission Example:

- - - ); -} +```svelte + + +{#if form?.success} +success +{:else} +
+ + + +
+{/if} ``` -## Configuration +## Examples -To configure the CSRF middleware function just pass an object containing your options to the initialization method: +See more examples in the [examples](examples) directory in this repository: -```javascript -const csrfProtect = csrf({ - cookie: { - name: '_myCsrfSecret' - }, - secretByteLength: 20 -}); -``` +| Framework | Implementation | +| ------------------------- | --------------------------------------------------------------------------------------- | +| Next.js 13 (app router) | [HTML form](examples/next13-approuter-html-submission) | +| Next.js 13 (app router) | [JavaScript (dynamic)](examples/next13-approuter-js-submission-dynamic) | +| Next.js 13 (app router) | [JavaScript (static)](examples/next13-approuter-js-submission-static) | +| Next.js 13 (pages router) | [HTML form](examples/next13-pagesrouter-html-submmission) | +| Next.js 14 (app router) | [HTML form](examples/next14-approuter-html-submission) | +| Next.js 14 (app router) | [JavaScript (dynamic)](examples/next14-approuter-js-submission-dynamic) | +| Next.js 14 (app router) | [JavaScript (static)](examples/next14-approuter-js-submission-static) | +| Next.js 14 (app router) | [Sentry](examples/next14-approuter-sentry) | +| Next.js 14 (app router) | [Server action (form)](examples/next14-approuter-server-action-form-submission) | +| Next.js 14 (app router) | [Server action (non-form)](examples/next14-approuter-server-action-non-form-submission) | +| Next.js 14 (pages router) | [HTML form](examples/next14-pagesrouter-html-submission) | +| SvelteKit (vercel) | [HTML form](examples/sveltekit-vercel) | +| SvelteKit (cloudflare) | [HTML form](examples/sveltekit-cloudflare) | -Here are the default configuration values: +## Configuration ```javascript // default config @@ -270,6 +225,10 @@ Here are the default configuration values: } ``` +## API + + + ## Development ### Get the code @@ -284,10 +243,10 @@ pnpm install ### Run the unit tests -Edge-CSRF uses jest for testing (via vitest). To run the tests, use the `test` command: +Edge-CSRF uses jest for testing (via vitest). To run the tests in node, edge and miniflare environments, use the `test-all` command: ```console -pnpm test +pnpm test-all ``` The test files are colocated with the source code in the `src/` directory, with the filename format `{name}.test.ts`.