kubetail-org / edge-csrf

CSRF protection library for JavaScript that runs on the edge runtime (with Next.js, SvelteKit, Express, Node-HTTP integrations)
MIT License
140 stars 7 forks source link

Does not work in XHR requests if token passed in body #14

Closed Janhouse closed 11 months ago

Janhouse commented 11 months ago

Currently I am unable to get edge-csrf working with server actions or regular ajax forms in Next.js 13.5.3.

It works only if I pass x-csrf-token in header, but it doesn't work if passed as csrf_token in form input. And while regular forms with XHR don't modify the input names, server actions also prefix the input names, so csrf_token becomes 1_csrf_token

Not sure if it is relevant, but I also upload files so the request is a POST with multipart body. And I have set cookie httpOnly to false.

amorey commented 11 months ago

Sorry you're having problems. Have you looked at the "js-example" here: https://github.com/amorey/edge-csrf/tree/main/example-ts-appdir/app

Can you post code/snippets to show the problems you're running into?

Janhouse commented 11 months ago

@amorey here are the two cases where it fails with 403: https://github.com/Janhouse/test-edge-csrf

amorey commented 11 months ago

Hi @Janhouse, sorry for the late reply (kids have been home sick). The problem is that the content-type of the request initiated by fetch() is "text/plain;charset=UTF-8" so edge-csrf doesn't know how to parse out the token from the body. If you add the token to the header then the requests should pass validation: https://github.com/amorey/edge-csrf/blob/main/example-ts-appdir/app/js-example/page.tsx#L27

Let me know if that helps.

Janhouse commented 11 months ago

@amorey thanks for the reply!

There are two examples.

The server action one generates a request with content-type multipart/form-data, but has the csrf_token field prefixed with 1_ image

The Form example also creates a request with content-type multipart/form-data and has csrf_token but it still does not work.

image

multipart/form-data is a standard for posting forms, it is not some random format I came up with. :roll_eyes:

While I understand that edge-csrf might not be ready for server components and the prefix 1_, the Form example should just work without passing additional headers.

amorey commented 11 months ago

Sorry, I forgot about the other examples...

In terms of file uploads using built-in html forms, I modified edge-csrf to accept content-type multipart/form-data and added an example that uses the browser's built-in html form submission here: https://github.com/amorey/edge-csrf/blob/main/example-ts-appdir/app/html-example/page.tsx#L33-L52

The code is live on NPM under version v1.0.5-rc1: https://www.npmjs.com/package/edge-csrf/v/1.0.5-rc1

Do you know where I can find the Server Action spec? Or do you know how to identify the prefix value from the request itself? Is it possible for it to have a value other than "1_"?

Let me know if v1.0.5-rc1 fixes the problem with html file uploads and I can bump it to stable.

Janhouse commented 11 months ago

@amorey great, just tried it, it fixes the multipart form. :+1:

I don't know where the server action spec is. Could try searching in next.js repos for $ACTION_ID since I see it adds those to every form with server action.

But seems like it always keeps the prefix at 1_. Having multiple forms on the same page still shows 1_. Having multiple pages and multiple actions also keep it as 1_ :shrug:

amorey commented 11 months ago

Great, thanks for checking. I added undocumented, experimental support for server actions by checking for 1_csrf_token so your code should work now. I'll keep an eye out for more details about server actions to make this support more robust in the future. Here's the new version (v1.0.5): https://www.npmjs.com/package/edge-csrf/v/1.0.5

Thanks again for your help!