Skip to content

Commit

Permalink
Add CSRF protection with remix-utils
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Jun 14, 2023
1 parent 34d0194 commit 6f1254f
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 52 deletions.
65 changes: 17 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,17 @@
<div align="center">
<h1 align="center"><a href="https://www.epicweb.dev/epic-stack">The Epic Stack 🚀</a></h1>
<strong align="center">
Ditch analysis paralysis and start shipping Epic Web apps.
</strong>
<p>
This is an opinionated project starter and reference that allows teams to
ship their ideas to production faster and on a more stable foundation based
on the experience of <a href="https://kentcdodds.com">Kent C. Dodds</a> and
<a href="https://github.com/epicweb-dev/epic-stack/graphs/contributors">contributors</a>.
</p>
</div>

```sh
npx create-remix@latest --typescript --install --template epicweb-dev/epic-stack
```

[![The Epic Stack](https://github.com/epicweb-dev/epic-stack/assets/1500684/345a3947-54ad-481d-888a-dbc1d1f313c1)](https://www.epicweb.dev/epic-stack)

[The Epic Stack](https://www.epicweb.dev/epic-stack)

<hr />

## Watch Kent's Introduction to The Epic Stack

[![screenshot of a YouTube video](https://github.com/epicweb-dev/epic-stack/assets/1500684/6beafa78-41c6-47e1-b999-08d3d3e5cb57)](https://www.youtube.com/watch?v=yMK5SVRASxM)

["The Epic Stack" by Kent C. Dodds at #RemixConf 2023 💿](https://www.youtube.com/watch?v=yMK5SVRASxM)

## Docs

[Read the docs](https://github.com/epicweb-dev/epic-stack/blob/main/docs)
(please 🙏).

## Support

- 🆘 Join the
[discussion on GitHub](https://github.com/epicweb-dev/epic-stack/discussions)
and the [KCD Community on Discord](https://kcd.im/discord).
- 💡 Create an
[idea discussion](https://github.com/epicweb-dev/epic-stack/discussions/new?category=ideas)
for suggestions.
- 🐛 Open a [GitHub issue](https://github.com/epicweb-dev/epic-stack/issues) to
report a bug.

## Thanks

You rock 🪨
# Epic Stack with CSRF Protection

This is an example of how to integrate the
[`remix-utils`](https://github.com/sergiodxa/remix-utils) package utilities for
[Cross-Site Request Forgery (CSRF)](https://en.wikipedia.org/wiki/Cross-site_request_forgery)
protection with the Epic Stack. The easiest way to explore the example is to
pull up
[the commit history](https://github.com/kentcdodds/epic-stack-with-csrf/commits/main).

Following the steps laid out in the Remix Utils docs is sufficient for this:

1. Install `remix-utils`
2. Generate the authenticity token in the `root.tsx` loader (be certain to
commit the session to set the cookie)
3. Wrap the App in the `<AuthenticityTokenProvider />` and provide the token
4. Render a Form with the `<AuthenticityTokenInput />` component
5. Verify in the Action using `verifyAuthenticityToken` and the session.
18 changes: 17 additions & 1 deletion app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { getUserImgSrc } from './utils/misc.ts'
import { useNonce } from './utils/nonce-provider.ts'
import { makeTimings, time } from './utils/timing.server.ts'
import { useOptionalUser, useUser } from './utils/user.ts'
import { commitSession, getSession } from './utils/session.server.ts'
import { AuthenticityTokenProvider, createAuthenticityToken } from 'remix-utils'

export const links: LinksFunction = () => {
return [
Expand Down Expand Up @@ -74,6 +76,8 @@ export const meta: V2_MetaFunction = () => {
}

export async function loader({ request }: DataFunctionArgs) {
const cookieSession = await getSession(request.headers.get('Cookie'))
const token = createAuthenticityToken(cookieSession)
const timings = makeTimings('root loader')
const userId = await time(() => getUserId(request), {
timings,
Expand All @@ -100,6 +104,7 @@ export async function loader({ request }: DataFunctionArgs) {

return json(
{
csrf: token,
user,
requestInfo: {
hints: getHints(request),
Expand All @@ -114,6 +119,7 @@ export async function loader({ request }: DataFunctionArgs) {
{
headers: {
'Server-Timing': timings.toString(),
'Set-Cookie': await commitSession(cookieSession),
},
},
)
Expand Down Expand Up @@ -185,7 +191,17 @@ function App() {
</html>
)
}
export default withSentry(App)

function AppWithCSRF() {
const data = useLoaderData<typeof loader>()
return (
<AuthenticityTokenProvider token={data.csrf}>
<App />
</AuthenticityTokenProvider>
)
}

export default withSentry(AppWithCSRF)

function UserDropdown() {
const user = useUser()
Expand Down
24 changes: 24 additions & 0 deletions app/routes/settings+/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import {
usernameSchema,
} from '~/utils/user-validation.ts'
import { twoFAVerificationType } from './profile.two-factor.tsx'
import { AuthenticityTokenInput, verifyAuthenticityToken } from 'remix-utils'
import { getSession } from '~/utils/session.server.ts'
import { GeneralErrorBoundary } from '~/components/error-boundary.tsx'

const profileFormSchema = z.object({
name: nameSchema.optional(),
Expand Down Expand Up @@ -61,6 +64,8 @@ export async function loader({ request }: DataFunctionArgs) {
}

export async function action({ request }: DataFunctionArgs) {
let session = await getSession(request.headers.get('Cookie'))
await verifyAuthenticityToken(request, session)
const userId = await requireUserId(request)
const formData = await request.formData()
const submission = await parse(formData, {
Expand Down Expand Up @@ -180,6 +185,7 @@ export default function EditUserProfile() {
</div>
</div>
<Form method="POST" {...form.props}>
<AuthenticityTokenInput />
<div className="grid grid-cols-6 gap-x-10">
<Field
className="col-span-3"
Expand Down Expand Up @@ -264,3 +270,21 @@ export default function EditUserProfile() {
</div>
)
}

export function ErrorBoundary() {
return (
<GeneralErrorBoundary
statusHandlers={{
422: () => (
<p>
The form was submitted improperly. Please{' '}
<Link to="." className="underline">
try again
</Link>{' '}
and be sure to not modify the form submission data.
</p>
),
}}
/>
)
}
115 changes: 112 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"react-dom": "^18.2.0",
"remix-auth": "^3.4.0",
"remix-auth-form": "^1.3.0",
"remix-utils": "^6.4.1",
"tailwind-merge": "^1.13.1",
"thirty-two": "^1.0.2",
"tiny-invariant": "^1.3.1",
Expand Down

0 comments on commit 6f1254f

Please sign in to comment.