kit icon indicating copy to clipboard operation
kit copied to clipboard

option to disable CSRF for list of endpoints

Open ghostebony opened this issue 3 years ago • 8 comments

Describe the problem

With the new csrf protection I can't receive webhooks without disabling csrf on all routes

Describe the proposed solution

In the csrf config, it would be nice to have a "exclude" setting to disable csrf protection in some routes.

// svelte.config.js

const config = {
    kit: {
        csrf: {
            checkOrigin: true,
            // exclude: [ "/webhooks/*" ],
            // AND/OR
            // exclude [ "/webhooks/1", "/webhooks/2" ]
        },
    },
};

export default config;

and/or

// src/routes/+(page.)server.js

export const csrf = false;

Alternatives considered

No response

Importance

would make my life easier

Additional Information

No response

ghostebony avatar Sep 13 '22 22:09 ghostebony

This is fairly easy to do now simply be reproducing SvelteKit's current CSRF check logic (it's pretty simple) in your handle hook, but wrapped in whatever additional check with event.routeId you want to include/exclude certain endpoints.

If we do add more customization here, it will likely be something more along the lines of code rather than configuration. That is, a new function exported by SvelteKit exposing its default CSRF handling that you can conditionally call in your handle hook or in a +page.server.js.

Conduitry avatar Sep 14 '22 00:09 Conduitry

Adding an additional use-case here.

My app uses an OpenID integration, where on success, the OpenID provider POST's back to the app.

This doesn't work without a way to exclude certain endpoints from CSRF protection.

dangreaves avatar Nov 14 '22 04:11 dangreaves

@Conduitry @Rich-Harris Is it possible to disable CSRF origin checks for specific routes through the handle hook? If so how?

sidharthramesh avatar Nov 17 '22 14:11 sidharthramesh

My blog app uses Webmention, which works by having remote server's POST urlencoded form data to a special endpoint. I wish sveltekit let me export something in the +server.ts file that disabled csrf form submission protection selectively for just that. Or alternatively, disable it for server endpoints but not for form actions.

Bluebie avatar Nov 25 '22 02:11 Bluebie

This would make things so much easier

kyuuaria avatar Dec 21 '22 21:12 kyuuaria

As conduitry hinted at this can be done manually with hooks, so until something is implemented I am doing this in hooks.server.ts:

import { error, json, text, type Handle } from '@sveltejs/kit';

/**
 * CSRF protection copied from sveltekit but with the ability to turn it off for specific routes.
 */
const csrf =
	(allowedPaths: string[]): Handle =>
	async ({ event, resolve }) => {
		const forbidden =
			event.request.method === 'POST' &&
			event.request.headers.get('origin') !== event.url.origin &&
			isFormContentType(event.request) &&
			!allowedPaths.includes(event.url.pathname);

		if (forbidden) {
			const csrfError = error(
				403,
				`Cross-site ${event.request.method} form submissions are forbidden`,
			);
			if (event.request.headers.get('accept') === 'application/json') {
				return json(csrfError.body, { status: csrfError.status });
			}
			return text(csrfError.body.message, { status: csrfError.status });
		}

		return resolve(event);
	};
function isContentType(request: Request, ...types: string[]) {
	const type = request.headers.get('content-type')?.split(';', 1)[0].trim() ?? '';
	return types.includes(type);
}
function isFormContentType(request: Request) {
	return isContentType(request, 'application/x-www-form-urlencoded', 'multipart/form-data');
}

export handle = csrf(['/auth/callback/apple']);

this basically reimplements the csrf handling built in to sveltekit, but allows a list of pathnames that can bypass the protection, so you will need to disable the built in behaviour in your svelte.config.js

csrf: {
	checkOrigin: false,
},

jamesbirtles avatar Feb 03 '23 16:02 jamesbirtles

As conduitry hinted at this can be done manually with hooks, so until something is implemented I am doing this in hooks.server.ts

Found this while trying to whitelist apple oauth to POST to a specific api/auth/callback. I added validation to the origin so only a specific origin (appleid.apple.com) could post to my callback url

const csrf =
	(allowedPaths: string[]): Handle =>
	async ({ event, resolve }) => {
		const origin = event.request.headers.get('origin') || ''
		const referer = new URL(event.request.headers.get('referer')).hostname;
		const forbidden =
			event.request.method === 'POST' &&
			origin !== event.url.origin &&
			!((origin === 'https://appleid.apple.com' || referer === 'appleid.apple.com') && allowedPaths.includes(event.url.pathname)) &&
			isFormContentType(event.request);
			
			/* rest of code */

hjaber avatar Jun 07 '23 14:06 hjaber