jarle / remix-adonisjs

Build fullstack Remix applications powered by AdonisJS
https://matstack.dev/remix-adonisjs
MIT License
49 stars 4 forks source link

Can't make the CSRF shield work #69

Closed Bricklou closed 3 days ago

Bricklou commented 1 month ago

As the title said, i'm trying to make CSRF work, everything is there (the cookie and the headers), but for some reason, it still doesn't want to work properly.

Fawwaz-2009 commented 3 weeks ago

I could be wrong and I'll let @jarle correct me on this, but from how the project is configured, I don't think you can added the CSRF token at the headers and expect it to work with adonis.js. here is my thinking, in adonis js the CSRF protection is handled by the shield module., you can see that shield module has a middleware that will run on every request.

image

meaning that this will run and possibly intercept requests before the request reach the remix app, think you attempt to fire an action, before the action logic run, the shield middleware if doesn't see the CRSF token it expect it would stop the request.

image

So the way I understand it, there are two options.

jarle commented 2 weeks ago

@Bricklou You can get CSRF working by adding a hidden input field to the form, you can see how the native edge helper csrfField() mentioned in the AdonisJS docs does that here: https://github.com/adonisjs/shield/blob/7182ed04ede95dddb7f0ce3a426f7c20ae7aa075/src/guards/csrf.ts#L161

To get it working with Remix, we can use a similar approach to what @Fawwaz-2009 outlined:

  1. Get the CSRF token value from the AdonisJS HTTP context
  2. Pass the CSRF value to our Page component through a Remix loader
  3. Include the CSRF value in a hidden field for any requests

Here is a very small example I created with the reference application in this repo, on the /login route (I omitted the action function):

export const loader = ({ context }: LoaderFunctionArgs) => {
  return {
    csrf: context.http.request.csrfToken
  }
}

export default function Page() {
  const { csrf } = useLoaderData<typeof loader>()

  return (
    <div className="container">
      <h1>Log in</h1>
      <Form method="post">
        <input type='hidden' name='_csrf' value={csrf} />
        <label>
          Email
          <input type="email" name="email" />
        </label>
        <label>
          Password
          <input type="password" name="password" />
        </label>
        <button type="submit">Login</button>
      </Form>
    </div>
  )
}

If you want to use the XSRF-TOKEN in the header of your request instead of this approach, I believe you have to use the useFetcher hook for your forms and set the header on each request.

I will create a section for this in the documentation when I get time!

jarle commented 3 days ago

This approach is now documented here: https://matstack.dev/remix-adonisjs/recipes/csrf-protection