Current section: Cross-Site Request Forgery 4 exercises

Creating and Managing CSRF Tokens With Node.js


00:00 So the first thing we're going to want to do is create a utility for managing the CSRF utility. So csrf.server.ts in our utils here, and the CSRF utility is created with new CSRF coming from RemixUtils CSRF server.

00:18 Then we've got a couple of options that we can provide here. So cookie being the primary one, and we need to create a cookie object. So we're going to create a cookie using createCookie from RemixRunNode. So our cookie will be equal to createCookie.

00:38 We're going to call this cookie CSRF, and this is just a mechanism for storing a value in the browser's cookie jar. So we're going to configure a couple of things here, and we'll dive deeper into cookies when we get to authentication. But we'll just give you a little bit of a peek

00:56 into the cookie config right here. So HTTP-only, we want that actually to be true. We don't want client-side JavaScript to be able to inspect this cookie. We also have our path. We want this to apply to the entire site, so we'll just say slash right here, and we'll set secure to

01:16 surprisingly if the node environment is production. During development, we're running HTTP localhost. I don't want to run an HTTPS server locally. You totally can. I don't really care to. Chromium-based browsers are totally fine with that. They'll say, oh yeah, it's a secure cookie, but you're working localhost, so no big deal.

01:35 But Safari is not super jazzed about that, so we're going to set secure to based only if we're in production. Then we're going to do same site lacks. That means if a site links over to our site, then those requests will send the cookie. That is desired behavior.

01:52 In fact, I think that may even be the default behavior. Then finally, we've got a bit of a tricky one, and this is secrets. Secrets allows us to say, we want to have this cookie be signed in a way that says, this cookie was absolutely created by you.

02:10 By providing a secret that only we know, then we can create cookies that we know for certain are created by us and not created by other individuals, nefarious actors. What happens is, it will encode the cookie into some funny looking value, which can be decoded,

02:29 but it includes a signed portion, so that when we come back and try to read that cookie, we're going to check the signed portion and see whether or not we were the ones who created the cookie, because you can only create that signed portion if you have access to these secrets. For that secrets bit,

02:48 we're going to go to our ENV and we'll add a session secret environment variable. This you would typically set as part of your environment. With Fly, you have Fly secrets set and all that. But during local development, you'll typically have a .ENV file that you just set things to,

03:06 so say super duper secret or something like that. Then you'll just make sure that this is set in production. But you don't have to just pray that it's set in production. We can actually verify that it's going to be set by going to our ENV server utility here and

03:23 adding the session secret that should be a string. If it's not there, then it's going to throw an error, and we'll be in a good place. We'll at least know what went wrong there. We've got our secrets. We're going to say process ENV.SessionSecret, and we'll split it by comma.

03:42 The reason we split it by comma is because secrets is actually expects an array. The reason it expects an array of strings is so that if somehow your secret were to get leaked, then you want to make sure you rotate your secrets or you get a new one and reset that. But if you were to just change it,

04:01 then all of the existing cookies would be invalid. To be able to continue to decode old ones and not fail on those, but make new ones with the new secret, we provide an array of secrets. The first one being the one that we're creating and signing new cookies with,

04:21 and then the other ones being ones that we'll check with when we verify whether a cookie is valid. Now, of course, if a secret really were to get leaked, then you'd probably want to just get rid of everything, and that would just be a really bad news for everybody and start fresh. But having a rotation of secrets is a pretty common practice just

04:39 to avoid unknowingly having a secret be leaked. This is just part of that rotation process to make it easier to rotate these secrets. You can add a new secret to the beginning of this array or this comma separated list of

04:57 session secret pieces in your environment variable. Great. With that cookie setup, we now have our CSRF all ready to go. We can export const CSRF, and that is going to be our CSRF utility. Now, there are some other options here,

05:14 but we only really need to specify the cookie for our use case. Now that we've got that setup, let's go to the root and we'll come up here to our loader. We're going to use the CSRF to get both the token generated as well as a set cookie header

05:32 so that the browser will store that cookie. We'll get our CSRF token and CSRF cookie header from the CSRF, which is our utility there, dot commit token, and we don't need to pass the honey prompts.

05:50 In fact, actually, I'll explain that here in a second. This is going to be async, so we're going to await that. Then we'll add our set cookie header here. Headers right here, and then set cookie. This will be our CSRF cookie header,

06:09 and then our CSRF token is going to be passed along with our JSON. Then we'll use this here in the next step of the exercise to provide that to everything. But suffice it to say, right now, we are setting the cookie header. Before we actually look at that though, we need to talk about the fact that

06:27 headers has got this red underline under it. That's TypeScript saying, hey, it's possibly null. This CSRF cookie header could be null, and you can't set a set cookie to null, which I suppose that makes sense. We'll just say, if it does exist, then we'll set it to this object. Otherwise, we'll set it to this one.

06:46 This is empty object of headers. Yeah, there we go. Make it just one gigantic line. The reason that it could possibly be null is, we don't want to have a new CSRF token generated every single time the user lands on the page,

07:04 especially if they have two tabs open. They open up one tab, they open up a second tab, that second tab set the cookie. It's a new CSRF header, and this tab has the old one. Then this tab submits, and it's going to be the wrong CSRF token. We need to preserve that token

07:22 as the user opens up a bunch of tabs. The way we do this is, we get the request from the data function args, and then we pass the request to the commit token. Then commit token will then read the cookie header, and if there's already a CSRF token, then it will use that existing one,

07:40 and it will return null for the set cookie header. Let's see all this in action. We'll pull up our DevTools. We'll look at our network tab, and we'll scooch this over just a bit. When I refresh the page, and we should see a set cookie header right there. There's the CSRF.

07:59 It's got the funny encoding because we're encoding that and signing it. Then we've got our path and HTTP only and same site lacks, all the things we configured it to do, and then we can look at the application tab and see our cookie is a happy-go-lucky right there. Then also because we are providing this request,

08:18 if I refresh, then that value should be the same, and I think it's the same. You can pause the video, I suppose, and check, but that looks the same to me. We're all set with those changes to make it so that we pass that token along with any form submissions

08:35 and prevent these terrible cross-site request forgery attacks. So really quick to review. First, we made a cookie and then used that to create our CSRF utility, and then we updated our loader to pass that CSRF token

08:52 and set that cookie header as necessary. That is the setup piece of getting our CSRF token into the browser.