DEV Community

Maxiviper117
Maxiviper117

Posted on

πŸ” Securing Your SvelteKit App Against CSRF Attacks Using Custom Hooks

Image description

Cross-Site Request Forgery (CSRF) is a vulnerability that lets attackers trick your users into unintentionally submitting requests. This guide demonstrates how to implement robust CSRF protection in SvelteKit using custom middleware hooks.


πŸ“Œ Step 1: Disable Built-in SvelteKit CSRF Check

SvelteKit provides default CSRF checks. To enable custom handling, disable the built-in check first.

πŸ“‚ svelte.config.ts

// svelte.config.ts import adapter from '@sveltejs/adapter-auto'; export default { kit: { adapter: adapter(), csrf: { checkOrigin: false, // Disable built-in origin checking to allow custom middleware }, }, }; 
Enter fullscreen mode Exit fullscreen mode

πŸ›‘οΈ Step 2: Create Custom CSRF Middleware

Create the custom CSRF middleware hook file to provide enhanced security and configurability.

πŸ“‚ src/hooks/csrf.ts

// src/hooks/csrf.ts import type { Handle } from '@sveltejs/kit'; import { json, text } from '@sveltejs/kit'; /** * Custom CSRF Protection Middleware * * @param allowedPaths - List of URL paths that bypass CSRF protection. * @param allowedOrigins - Trusted origins allowed to make cross-origin form submissions. */ export function csrf(allowedPaths: string[], allowedOrigins: string[] = []): Handle { return async ({ event, resolve }) => { const { request, url } = event; // Get the 'origin' header from the incoming request const requestOrigin = request.headers.get('origin'); // Determine if the request comes from the same origin const isSameOrigin = requestOrigin === url.origin; // Check if the request origin is explicitly allowed (trusted external origins) const isAllowedOrigin = allowedOrigins.includes(requestOrigin ?? ''); // Define conditions under which the request is forbidden (potential CSRF attack) const forbidden = isFormContentType(request) && // Checks if the request contains form data ['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method) && // State-changing methods !isSameOrigin && // Origin mismatch !isAllowedOrigin && // Not explicitly allowed !allowedPaths.includes(url.pathname); // Path not explicitly allowed // If forbidden, return a 403 Forbidden response immediately if (forbidden) { const message = `Cross-site ${request.method} form submissions are forbidden`; // Return JSON or plain text based on request headers if (request.headers.get('accept') === 'application/json') { return json({ message }, { status: 403 }); } return text(message, { status: 403 }); } // If the request passes CSRF checks, continue to the next middleware or endpoint return resolve(event); }; /** * Helper function to check if request 'origin' is allowed. */ function isAllowedOrigin(requestOrigin: string | null, allowedOrigins: string[]) { return allowedOrigins.includes(requestOrigin ?? ''); } /** * Helper function to determine if request content-type indicates a form submission */ function isFormContentType(request: Request) { const type = request.headers.get('content-type')?.split(';', 1)[0].trim().toLowerCase() ?? ''; return ['application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain'].includes(type); } } 
Enter fullscreen mode Exit fullscreen mode

🚩 Step 3: Integrate the Middleware into SvelteKit's Hooks

Integrate this middleware using SvelteKit's sequence helper, allowing you to chain multiple middleware cleanly.

πŸ“‚ src/hooks.server.ts

// src/hooks.server.ts import { sequence } from '@sveltejs/kit/hooks'; import { csrf } from './hooks/csrf'; // Define paths exempt from CSRF checks (e.g., public forms or APIs) const allowedPaths = ['/api/public-form']; // Define trusted origins allowed to make cross-origin form submissions const allowedOrigins = ['https://trusted-site.com', 'http://localhost:5173']; // Export the combined hooks using 'sequence' for better flexibility export const handle = sequence( csrf(allowedPaths, allowedOrigins) // CSRF hook added here // You can chain additional middleware hooks here if needed ); 
Enter fullscreen mode Exit fullscreen mode

βœ… Step 4: Testing Your CSRF Middleware

Allowed Requests (should pass):

  • Form submission from same-origin (e.g., https://your-site.com β†’ https://your-site.com/api).
  • Cross-origin submission from explicitly allowed origin (https://trusted-site.com) to an explicitly allowed path (/api/public-form).

Blocked Requests (should fail with 403):

  • Cross-origin submission from a non-whitelisted domain.
  • Submission from an allowed origin (https://trusted-site.com) to a non-allowed path.
  • Submissions from unlisted origins/domains.

βš™οΈ Step 5: Integrate Using SvelteKit sequence Hook

Use SvelteKit’s built-in sequence function to combine your CSRF middleware with other hooks seamlessly.

πŸ“‚ src/hooks.server.ts

// src/hooks.server.ts import { sequence } from '@sveltejs/kit/hooks'; import { csrf } from './hooks/csrf'; // Initialize middleware with explicit allowed paths and origins const csrfProtection = csrf( ['/api/public-form'], // paths exempt from CSRF protection ['https://trusted-site.com', 'http://localhost:5173'] // trusted cross-origin sites ); // Export the combined hook using SvelteKit’s 'sequence' export const handle = sequence(csrf(['/api/public-form'], ['https://trusted-site.com'])); 
Enter fullscreen mode Exit fullscreen mode

⚠️ Important Formatting Notes for Allowed Origins

When defining allowed origins, remember:

βœ… Correct Examples:

['https://trusted-site.com', 'http://localhost:5173'] 
Enter fullscreen mode Exit fullscreen mode

❌ Incorrect Examples (these won't work!):

  • ❌ No protocol: 'trusted-site.com'
  • ❌ Wildcards not supported: '*.example.com'
  • ❌ Paths not allowed: https://example.com/path

πŸŽ‰ Summary & Final Thoughts

You've successfully implemented custom CSRF protection in your SvelteKit application:

  • βœ… Custom middleware with explicit allowed origins and paths.
  • βœ… Flexible configuration via hooks.
  • βœ… Clear error messaging for blocked requests.

This approach provides powerful protection against CSRF attacks without restricting legitimate cross-origin usage.

Stay secure, and happy coding! πŸš€βœ¨

Top comments (0)