Using NPM cookie-parser to Read Cookies in React
We'll utilize the `cookie-parser` package in our Node server to help parse a "viewer" cookie from HTTP requests sent from the client.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two, plus 70+ \newline books, guides and courses with the \newline Pro subscription.
[00:00 - 00:14] To integrate cookies into our node express server application, we'll use the cookie parser package provided to us by the express team. Cookie parser helps parse HTTP requests that can be applied as an express middleware.
[00:15 - 00:32] We'll head to the terminal and install the cookie parser package to our server project. To get the appropriate type definitions, we'll also install the type definitions file from definitely typed as a development dependency.
[00:33 - 00:58] In order for our server app to parse incoming cookies from our client, we'll need to make some small changes. Cookie parser takes in an optional secret parameter. If this secret is set, then cookies can be signed with this secret, which helps ensure that cookies aren't to be tampered with.
[00:59 - 01:13] To ensure the secret we'll apply is a secret that can be specified as part of the application's configuration, we'll declare a secret in our server.env file. We'll create a new environment variable called secret.
[01:14 - 01:23] And this isn't something that we have to specify with a certain value. So for our development environment, we'll say something along the lines of this as a secret.
[01:24 - 01:59] In production, it'll be more appropriate to use a longer, more randomly generated string, something that resembles more like our Google client ID or client secret. We'll now import the cookie parser package in our source index file and apply cookie parser as an application middleware.
[02:00 - 02:15] We'll pass in the optional secret value by referencing the secrets in our environment configuration. We'll want our viewer resolver functions to be able to read and set a cookie depending on whether the viewer is logging in or logging out.
[02:16 - 02:30] To read or set a cookie, we'll need access to the request and response objects in our resolver functions. To do so, we'll probably want to introduce the request and response objects as part of the context of our resolver functions.
[02:31 - 02:47] The context function of our Apollo server constructor is run with the request and response objects for every request. So we can simply access these properties and pass them along as part of the context object for all our resolvers.
[02:48 - 02:58] We'll now head over to our viewer resolvers map and look to utilize cookies in the log in and log out resolver functions. There's a few things we're going to do here.
[02:59 - 03:12] The login via Google function that runs when the user signs in with Google will be modified to now create a cookie. Our log out resolver function will be modified to clear the cookie when the user logs out.
[03:13 - 03:33] And lastly, we'll create a login via cookie function that can be run as part of our login resolver which will help the user login via cookie instead of a Google authorization code. When we send the cookie, we can do so with the response.cookie function available to us thanks to the cookie parser package.
[03:34 - 03:53] In these cases, we're able to pass an options object that will help us create a secure cookie. Since we'll use this options object in a few different cases, let's create this options object at the top of the file as cookie options.
[03:54 - 04:10] We'll set the HTTP only flag to true to ensure the cookie is not to be accessible by client JavaScript. This would help counter cross-site scripting attacks. We'll set the same site flag to true to ensure the cookie is not sent with cross-site requests.
[04:11 - 04:28] The same site flag is available in most modern browsers and helps counter cross -site request forgery attacks. The signed property will help ensure the cookie is not to be tampered with by creating an HMac of the value and base 64 encoding it, so we'll set it to true as well.
[04:29 - 04:41] The secure property ensures the cookie can only be sent over HTTPS. We'll want this option in production, however in development we'll not want to have this set to true since local host is an HTTP.
[04:42 - 04:55] What we'll do here is create an environment configuration value that we can call nodeenv that we'll check for if it's in development. If so, we'll have this property set to false, otherwise we'll have it set to true.
[04:56 - 05:19] We'll now go ahead and actually create this environment configuration value and in development we'll set its value to development. In the login via Google function after a user successfully signs in, we'll look to set a new cookie with the response cookie function.
[05:20 - 05:48] To have the response object available, we'll state the response object will be a parameter of this function and we'll assign its type to the response interface we can import from express. We'll label the key of this cookie as viewer and for the value of this cookie we'll use the user ID.
[05:49 - 06:17] Now one could take an additional security step here to encode this user ID value as we set the cookie and decode it when we attempted to retrieve the cookie, but since we're already signing the cookie, we won't take this additional step. We'll declare the options of the cookie with the cookie options we've created above, but we'll introduce one other option in this case, which is the max age option, which helps set the maximum age of our cookie in maximum age.
[06:18 - 06:28] We'll call the cookie in milliseconds format. We wouldn't want this cookie to expire any time soon, so for our use case we'll set the expiration to one year.
[06:29 - 06:54] In the logout resolver, we'll look to clear this viewer cookie when the viewer ever signs out of our application. We'll access the response object from the context property and use the response.clearcookie function.
[06:55 - 07:17] We'll specify that the viewer cookie is the cookie we'll like to clear and we 'll pass the cookie options we specified as well. We're passing in cookie options here, since most web browsers will only clear the cookie if the given options here is identical to those given in the response.cookie function, excluding an expires or max age property.
[07:18 - 07:27] The last thing we'll do is create and use a login via cookie function. We'll create this login via cookie function right below login via Google.
[07:28 - 07:41] The login via cookie function will be fairly straightforward. It'll try to find a user document in our database by using the viewer ID retrieved from the viewer cookie found in our request.
[07:42 - 07:58] We'll say that the login via cookie function is to accept the user token, the database object, and the request and response properties. We'll set the type of the request property as the request interface will import from express.
[07:59 - 08:15] This function will be a promise that when resolved successfully should return an instance of a user or undefined. We'll use Mongo's find1 and update method.
[08:16 - 08:30] We'll state that we're interested in finding the viewer where the ID matches that from the viewer cookie in our request. Since our cookie is signed, we'll need to access this viewer cookie from the signed cookies property.
[08:31 - 08:53] If this viewer is found, we'll update the token field with the most recent randomly generated token from logging in. We'll set to the return original properties of false since we're interested in retrieving the updated value.
[08:54 - 09:15] We'll access the updated document by accessing the value of the results. Now if this viewer document doesn't exist, we'll clear out the cookie since this tells us the cookie doesn't have the appropriate viewer ID.
[09:16 - 09:28] If the viewer is found, we'll simply return it from our function. Let's see how we can use the login via cookie function in our login resolver.
[09:29 - 09:55] If a login mutation is ever fired from our client and a code isn't being passed in, this would entail the viewer will attempt a login from their cookie without the viewer actually knowing this. So in this case, we'll call the login via cookie function.
[09:56 - 10:11] We'll update our login resolver to destruct the request and response properties from the resolver context. And we'll also update how we've called a login via Google by passing in the response object it now expects.
[10:12 - 10:21] And there we have it. The server is now prepared to set and clear a cookie that will help with pers isting login sessions after the initial Google sign-in.
[10:22 - 10:54] In the next lesson, we'll pick up the client side work to see how we can fire the login mutation whenever the app is being launched with which a cookie will now be passed along. In fact, now when we head to the client side of her app and log in, we'll see a cookie called viewer being set on our client browser by our server.
[10:55 - 11:01] If we sign out or log out of our application, this viewer cookie would be cleared. Amazing.