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)