kit
kit copied to clipboard
option to disable CSRF for list of endpoints
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
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.
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.
@Conduitry @Rich-Harris Is it possible to disable CSRF origin checks for specific routes through the handle hook? If so how?
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.
This would make things so much easier
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,
},
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 */