Hey guys, in this lesson we're going to talk all about Cookies and Sessions. 1
Now just a word of warning here during the lesson, if you continue watching, you may start to feel 2
that you get a little bit hungry. 3
So don't blame me if I've just completely derailed your diet plan, because in this lesson, we're going 4
to talk all about cookies and their yummy goodness. 5
Not really, the type of cookies that we're talking about that relates to web development are more similar 6
to fortune cookies, because they have a message that's packaged inside, and you can pass these around 7
and they can be broken to reveal the message. 8
You've probably come across cookies before, but if you haven't, I just want to show you how it works 9
in practice. 10
So let's say we go on to Amazon and we search for the switch, right? The Nintendo Switch. 11
And we decide to go ahead and add it to basket. 12
So now when we look inside our basket you can see we have one item; the switch. 13
Right? 14
And then we're going to do something that all e-commerce sites hate, 15
we're going to go and navigate away and abandon our shopping cart. 16
Now this to any e-commerce website is a real crime, because the user obviously wanted to buy something, 17
but then at some point, they got distracted by something else abandoning their cart without checking 18
out. 19
So what does Amazon do? 20
Well, as soon as you added that item to the cart, Amazon has created a cookie and has stored that 21
cookie on your browser. 22
So how can we go and view that Cookie well, inside Chrome, 23
if you go to Settings and you search for cookie. 24
If you scroll right to the bottom, it shows you that there's something relating to cookies inside Content 25
Settings. 26
So if we go over there and we click on Cookies, you can see there's this option to, "See all cookies 27
and site data." 28
And now you can see that Amazon has not added just one cookie, 29
just by doing that very small act of adding a Nintendo Switch to our Amazon basket, Amazon has told 30
our browser to save all sorts of information about ourselves. And if we click on amazon.co.uk, you 31
can see there's the session-token, the session-id and these cookies don't necessarily contain any actual 32
information, 33
say, "This user wanted to buy a Nintendo Switch," but what they do contain is an ID number, and this 34
ID will be used to fetch all of those things that you added to your cart during this browsing session 35
on Amazon. 36
And this is why, if you decide to go and close down your browser and you open it back up and let's 37
head back to amazon.co.uk, you can see that my switch is still inside the basket, so they haven't 38
forgotten this. 39
So that means the next time I go on to Amazon, that Nintendo Switch is still going to be in my shopping 40
basket. 41
However, if we go into those cookies for Amazon, 42
and we go ahead and delete it... 43
So let's go back to, "See all cookies and site data," 44
we find the one that is directly related to Amazon and we remove all of these. 45
Now if we go and refresh this website, you can see we've forced it to forget our last browsing session, 46
and it doesn't know about that Nintendo Switch anymore. 47
Now on the internet, cookies are used widely to save these browsing sessions, and it goes beyond just 48
saving your last actions on the website. 49
When Amazon adds those cookies to my browser, it also means that when I go and visit another website, 50
say if I go on to Facebook, then it knows who I am and what items I wanted to buy on Amazon, and it 51
will try to remind me of that thing that I wanted to buy on Amazon. 52
And it's kind of creepy, 53
but this is essentially how retargeting ads work. 54
Once a user comes to your website, initiate some sort of buying behavior, and then they decide to 55
abandon cart, you save what it is that they wanted, and then on other websites, or when they come 56
back onto your website, you remind them about that thing that they wanted to buy. 57
And this is all done through cookies and sessions. 58
If we review this from a web development point of view, it means that, say, on day one when I go 59
into Chrome and I type in Amazon.com, my browser will make a GET request to Amazon's server requesting 60
for their home page. 61
Amazon Server will then respond to that request and send over the HTML, CSS, and JavaScript files 62
that are needed for my browser to be able to render the Amazon website. 63
And then let's say that we decided to add a computer to our cart. 64
Well, that is equivalent to making a POST request to Amazon server saying that, "I would like to buy 65
a computer." 66
Right? 67
And it's at this moment in time when Amazon servers will create a cookie that contains that data, 68
"This user wanted to buy a computer." And when it responds to the POST request, that cookie gets sent 69
along and the browser gets told to save that cookie. 70
So that means that if I now get distracted and I decide to go on to Facebook or whatever it may be, 71
but if I come back tomorrow, that cookie is still saved on my browser. 72
The next time that I make a GET request to Amazon server, that cookie gets sent along with my GET request 73
to allow the server to be able to identify who I am and see if I had any previous sessions on Amazon. 74
And it's the equivalent of cracking open that fortune cookie, revealing what were the previous things 75
that I wanted to buy. 76
So in this case it was a computer and then they could respond with the HTML, CSS, JavaScript and also 77
render my cart so that the computer is already added in the cart. 78
There are lots of different types of cookies, but the types of cookies that we're going to be looking 79
at, are the ones that are used to establish and maintain a session. 80
A session is a period of time when a browser interacts with a server. 81
Usually when you log in to a website, that's when your session starts, and that's when your fortune 82
cookie gets created. 83
And inside that fortune cookie, you'll have your user credentials that says, "This user is logged in 84
and has been successfully authenticated." 85
So that means as you continue to browse the website, you won't be asked to log in again when you try 86
to access a page that requires authentication, because they can always check against that active cookie 87
that you have on your browser, and it maintains your authentication for this browsing session until 88
the point when you log out, which is when this session ends, 89
and the cookie that's related to this session gets destroyed. 90
We're going to be implementing cookies and sessions into our website, and we're going to be doing it 91
using something called Passport. 92
Now, if you talk about NodeJS and authentication, it's almost impossible to not mention Passport. 93
And it's something that's very, very flexible and it allows you to authenticate your users using either 94
a local strategy like what we're doing right now, which is username and password, or use a whole bunch 95
of other services such as Google, Facebook, LinkedIn, Twitter. 96
And it makes it a lot easier for you to be able to plug these different ways of authentication into 97
your website. 98
Let's get started learning about Passport and learning about how we can implement cookies and sessions. 99
Again in this lesson, there is an associated download and that's a zipped file that when you unzip it should 100
be a folder with files called 9.3 Sessions & Cookies. 101
As always extract it, 102
open it in VS Code, run "npm install" once you have navigated to the correct location, and you'll notice that 103
we've added a couple of new packages in the dependencies for this project compared to last time. 104
The first one is something called "express-session" , which is the package that we're going to use to 105
enable this kind of session persistence, which allows us to save a cookie on the user's browser 106
and the next time they try to access our website, we check that cookie and make sure that we can bypass 107
that whole login process. 108
Another tool that we're using in order to work with Express Session is called Passport and Passport.js 109
is a really neat library because it allows us to add lots of different authentication strategies 110
into one project very easily. 111
You can see that there are 500 plus strategies, and that includes a lot of the most commonly used ones. 112
For example, if you wanted to allow people to log in with Twitter, if you wanted people to log in 113
with Facebook or Google or GitHub or Microsoft or just a whole bunch of different strategies, you can 114
implement passport and you'll be able to add each one of these strategies very easily, just as if you're 115
playing Lego. 116
So we're going to get started by implementing something called "passport-local", because at the moment 117
the only strategy we have is local login, which means username and password, or in our case it's email 118
and password. 119
To use it, we need the passport module as well as passport-local and also express-session. 120
So don't worry about spelling it wrong, don't worry about typing it wrong, 121
it's already included in the package.json, 122
so once you do that "npm install", those modules will get added automatically into your node_modules 123
folder. 124
The code in index.js is completely the same as before, but I want to walk you through step by step 125
how we can implement cookies, sessions and all in the package of using the passport-local strategy. 126
The first thing we're going to do is we're going to import something called session from our package, 127
"express-session". 128
And this is going to allow us to set up a new session to start saving user login sessions. 129
So we're going to use this as a middleware. 130
So use app.use(). 131
And then we tap into the session and we create a new instance and pass in some options. 132
What are the options we're going to add. 133
Most important we need a secret. 134
And this is the secret that's used to sign the session cookie. 135
So if you remember what we talked about encryption, this is quite important, 136
you can consider this as the key that's used to keep the session secret. 137
Because we're bypassing the login process, 138
we need to make sure that we are doing everything that we can in order to make sure that we are protecting 139
the user's login information. 140
You can set this to any string you like. 141
So for example, I'm just going to call it, "TOPSECRETWORD". 142
And in this case you can replace that with whatever you like, just as long as you make sure you add 143
one. 144
Next we're going to set resave, to false. 145
And what this option does is whether if we want to force a session to be saved into the store. 146
The great thing about session, is you pair it with Postgres and you can save the session to your database. 147
At the moment I'm not going to do that. 148
So I'm going to set this resave to false because we're going to save it in our app on our server for 149
now. 150
If you wanted to persist this, then you can look up how to use Express session with a Postgres store 151
to store it. 152
And that means that even if your server was reset or if you updated your server and restarted it, it 153
would still work for the user because that information would be on the database. For now, 154
I think this should do just fine. 155
So the final one I'm going to set is something called, "saveUninitialized". 156
And again this is to do with the whether or not we want to force a session to be saved to the store. 157
And I'm going to set this to true so that we actually store the uninitialized sessions into our server 158
memory. 159
Now, once we've done all of that, the next thing we're going to do is we're going to use Passport. 160
So I'm going to import passport from the "passport" module. 161
And it's really important that your passport module goes after your session initialization. 162
You should have already created your session before you use the next lines of code. 163
This is really important. If you get the order wrong in our middleware because we know it gets run 164
from top to bottom. 165
If you haven't got a session that you've already set up, and then you try to do the next thing, which 166
is again. middleware., 167
so app.use() and then it's passport.initialize() 168
and that requires some parentheses because it's a method. 169
And then it's app.use(passport.session). 170
This has to happen after we already have a session that's already ongoing, 171
otherwise it's not going to work. 172
So just make sure that you've got the right order in your code as well. 173
So now that we've done all of that, I'm going to show you what's a really cool thing that you can do, 174
because we're now using Passport and sessions. 175
At the moment we're deciding whether or not to show the user secrets.ejs, depending on whether 176
or not they've passed either registration or login, 177
but in most cases, you would probably want a route, right? 178
You would want an app.get() that goes to "/secrets", so that the user can hit up that path straight 179
away in their URL. 180
But this has its own challenges, because how do we know if they are already logged in? 181
Well, if we have an active session that's saved in a cookie, then we can straight away show them that 182
page, 183
but if we don't, then we shouldn't show it to them. 184
The way we do this is we can use something that gets saved into the request, which is called isAuthenticated, 185
and this comes from Passport and it allows us to check is the current user who's logged in in the current 186
session authenticated already. 187
And if that turns out to be true, then we can show them the secrets page. 188
So we can say if this is true, then in that case we'll res.render the secrets.ejs, 189
but otherwise, so, else, we're going to res.redirect to the login route. 190
That way they'll have to log in first before they see this. 191
If we go ahead and start up our server as it is, you'll see that if I try to go to that route, 192
"/secrets", it's going to redirect me straight into the login page. 193
And if you open up the Chrome Developer Tools, so you can either go through Settings, or you can use the 194
shortcut which I taught you previously, you'll be able to see that under Cookies, 195
you may or may not see localhost:3000. 196
All right. 197
So how can we implement login for the user who we already have stored on our database right here. 198
We saw that previously. 199
We can use their email and password to log in, 200
but now we want to be able to authenticate that session and start it up in Passport. 201
So the first thing we're going to do is we're going to import and then add a set of curly braces to 202
import something called {strategy}. 203
And this strategy comes from passport-local. 204
So this is actually also known as a local strategy. 205
Now the next thing we're going to do is we're going to scroll all the way to the bottom just above our 206
app.listen(), 207
and we're going to tap into passport, and say passport.use(), 208
and we're going to use this to register a strategy. 209
And the strategy is going to be a new strategy... 210
So we can create a new object from this class called strategy. 211
And inside here we're going to call something called verify(). 212
That's a function called verify(), 213
and it takes three parameters a username, a password, 214
and a callback, which in the Passport world is always written as cb. 215
So now we can open up our curly braces and start adding some code into this strategy. 216
The fact that it's a local strategy means it's trying to validate whether if a user already has the 217
right password, if their email exists in the database already, 218
so basically everything that we've already tried to do inside our login route. 219
So we can actually cut this entire try catch block and we can paste it straight in here. 220
There's one thing that we need to remember about Passport. 221
It can automatically through the use of this verify() function, grab hold of the username and password 222
from the form that submits the login request. 223
So if we look in login, you'll see that there is a field called username and a field called password, 224
and as long as that matches and we have username and password, and also username and password in the 225
name attribute here, then what it's going to do is it's going to automatically grab it... 226
And we can in fact confirm this by console logging this. 227
And instead of us having to use body-parser and grab hold of all of that stuff by using request.body.username, 228
etcetera, body-parser automates that for us. 229
The other thing we need to take a look at is because our database queries are asynchronous, so we need 230
to wait for them to happen before we continue, 231
we need to change this verify() function to an asynchronous function. 232
So that's an async function called verify(). 233
And as always how do I know all of this, 234
of course it's through reading the documentation. 235
If you go into the documentation section and you go to their tutorials and go to username and password, 236
then you'll see that there's several sections that we can skip. 237
We've already got a starting project, 238
We've already got our Login Prompt, 239
now this is where we're at at the moment. 240
We're working through this verify password section. 241
Now they use a different package to hash the passwords. 242
We're using Bcrypt and they're using Crypto, 243
and in fact more projects use Bcrypt in the real world than Crypto, 244
so don't worry about that. 245
You are in good company and we're kind of just working through this verify() function at the moment. 246
One of the important things to realize about how Passport works is it gets triggered whenever we try 247
to authenticate a user. 248
So it falls into this verify() function, 249
and the first thing we try to do as we did before is we're going to see if we have a user with the current 250
email. 251
And we're going to change this from email to username as well, so that we actually use the username 252
that comes from the form. 253
And if we do, namely the length is greater than 0, then we set them as the current user, including 254
all of the result that comes back from the database, which remember includes things such as their ID, 255
their email, their password hash. 256
Next, we use our Bcrypt to compare their password that was entered in the form versus the password 257
that was saved on the system, 258
and if that happens to be the same... 259
So let's go ahead and change this login password to the password from here. 260
And if that comparison succeeds then we will end up with a result that is equal to true. Here, 261
instead of simply res.rendering(secrets.ejs), we're going to try and pass a callback. 262
So we're going to return from this function which is this verify() function, 263
and we're going to return by calling the callback, 264
that's one of these inputs. 265
And the callback is going to firstly include any errors. 266
Well, if this result was true then that will be null, 267
no errors. 268
And then the next thing we're going to pass over is the details of the actual user. 269
We can save the details of the user from our database lookup, pass it through the callback, 270
and that means when we redirect to the next stage, which is to go to the "/secrets", in the 271
request already we will have the isAuthenticated set to true, and we even have access to something 272
called a user. 273
So I'm going to console.log() this a little bit later on, 274
but I'm just going to write it out so that you can see what it looks like, 275
req.user() will have the details that we have saved into it from this part where we got the user 276
from 277
the database, and then we added the user in to the callback. 278
What if things go wrong? 279
Well, if there was an error instead of console logging it, then we should return the callback and 280
pass the error here. 281
And also if the password was incorrect, then we should also return a callback. 282
And in this case it's not really an error because there's nothing wrong with what's happened with Passport 283
or with authentication, 284
it's actually a user error, which is not strictly an error. 285
And here we can set this second value the user to false. 286
And this basically tells us when we try to get hold of the user or when we check isAuthenticated 287
to false. 288
And that means we know that the user is not authenticated. 289
And then finally we've got this last res.send() which we're going to change into the same format, 290
return the callback, and we're going to set this as an error, 291
and we're going to set the error to "User not found". 292
And we might as well do it in case our database query goes wrong as well. 293
That's all there is to setting up our local strategy basically takes it out of the app.post() and puts 294
it into a separate section, which can be reused at any part of our code. 295
The last thing we need to do is just before the app.listen(), we're going to write something called, 296
passport.serializeUser, and this takes a function. 297
So let's add another set of parentheses inside the first set of parentheses. 298
And we get to serialize a user. 299
So that means that we can save the data of the user who's logged in to local storage. 300
And we can use the callback to pass over any of the details of the user. 301
Now, there is another Passport related method that we need just to make this work, which is called 302
deserializeUser(). 303
And it's exactly the same code, 304
it just passes along the user's information 305
and all that this does is it saves the user's information such as their ID, their email to the local 306
session, and then when you want to get hold of the user, it deserializes it back into a way that you 307
can access the user's information through that session. 308
Coming back to our app.post(), instead of handling everything inside an asynchronous function as we normally 309
do, grabbing hold of the request, sending back a response, et cetera, we're actually going to ignore 310
all of that, and we're going to use Passport as middleware. 311
So that means that straight after the comma, after the route, we're going to use passport.authenticate(), 312
and this is the method that is going to trigger the strategy down here as long as we tell it to. 313
So we're going to say use the local strategy in order to authenticate this particular request. 314
And what this is going to do is we're going to set some options, 315
first is a successRedirect. 316
Where do you want to redirect the user 317
if everything went well and they are successfully authenticated? We redirect them to "/secrets". 318
Now what about a failure redirect. 319
Well, if they got the password wrong, if it didn't work for any sort of reason, we're going to redirect 320
them back into the login page. 321
If all of that's a bit confusing, let's first just try it out. 322
So let's hit Save. 323
Make sure that we've got our server already running. 324
And let's go ahead and make sure that this actually works. 325
All right. 326
So now let's go ahead and test it out. 327
Let's go to our login page which we have added. 328
I'm going to put in my user with my email and password. 329
And you can see as soon as I click login it takes me to the secrets page which meant everything went 330
well. 331
And we can actually catch that thing that I told you about the user that got serialized into the session, 332
and then deserialized when we need to access it by tapping into req.user. We can console.log() it 333
when we actually head over to the secrets page. Let's go back to our login page. 334
Click login and you can see that req.user being printed here. 335
It gives me their ID, their email, their password, and all of these things are stored whenever an 336
authenticated request is made. 337
And this also means if I try to go to the secrets route again, if I try to hit refresh on this page, 338
or if I just go to it, or even if I close down my tab and reopen a new tab, you can see that my session 339
is now saved. 340
And if you go into the Chrome Developer Tools, which remember is over here under More Tools, Developer 341
Tools, you'll be able to go to the Application tab, 342
and here you can see under the Cookies section that for our URL localhost 3000, we've added a cookie 343
into here that helps us to persist this session. 344
This is why this is saved. 345
Now you might be wondering, well, how long is that session saved? 346
What if I closed down the browser and then I try to go to this again? 347
Will that work? 348
Well, no. 349
As you can see, I typed it in again and it told me to log in. 350
That's because that session cookie has now expired. 351
So how do we set the expiry? 352
Well, we can go into where we have our app.use() and we have our session, 353
one of the options is cookie. 354
And the cookie can have an option called maxAge, 355
and this specifies a number in milliseconds to use when calculating the expiry. 356
If we start out with 1000 milliseconds as one second, and then we times it by 60 to get one minute, 357
and then we times it by 60 to get one hour, and then maybe we times it by 24 to get one day, 358
then we end up with a one-day length cookie. 359
So now if I repeat that experiment again, let's go ahead and log in, copy our URL and close down this 360
browser and then put it back in here. 361
You can see my cookie is still valid. 362
It's still working. 363
So that's just one thing that you can mess around with depending on what your needs are. 364
The final thing we need to do for our session is what about the registration route? 365
We've got this strategy for login, which is all very well and good, but right now our register route 366
still tries to do a res.render. 367
We can update this very, very easily using another method that we get as a part of using Passport. 368
What we're going to change here is instead of simply just inserting the user and not caring about the results, 369
we're going to change this to say, insert into the user the new email and password, 370
but we're also going to use that returning SQL command in order to return everything that we get back 371
from the record that we just inserted. 372
So that means that we can actually get hold of the new user 373
through this result of this database query, the user gets inserted and then it comes back in the result 374
because we're using that returning function. 375
And then we can simply set the new user to equal the result.rows, 376
and we're going to tap into the first row. 377
And now instead of using res.render("/secrets), we're going to use req.login which comes from Passport, 378
and remember it is this particular login, 379
so i is lowercase. 380
This is important. 381
And inside here we're going to pass the user that we're going to save to the session. 382
That is going to be the same as the user we created just now. 383
And then it also allows us to add a callback in the case of what to do once everything is done. 384
If there are any errors, it'll be captured in here, and we can simply console.log() any errors if it exists, 385
otherwise, we can do a res.redirect and we can redirect to the secrets route. 386
And once we've called req.login(), 387
this automatically authenticates our user, passes this user's information into the session, serializes, 388
deserializes, 389
and when we get over here, we'll still be able to log that user right here. 390
We can check if they're authenticated just the same as when we do our login route. 391
And I'm going to show you this by creating a new user. 392
So let's go to the register route and let's create a new user. 393
So I don't have this email in my database yet I'm going to register her goes straight to the secrets 394
page, 395
and you'll see that user being printed right here from this line in the app.get() of the secrets route. 396
This is a really neat way of being able to get hold of your user data at any route or any part of your 397
server, and it's really useful when you want to check information for that user. 398
When you want to persist, user sessions, etc. even though it doesn't add a lot in terms of security, 399
it's a really important part of all modern websites 400
when we do authentication, that we actually persist their sessions. 401
In the next lesson, we're going to talk about something called Environment Variables. 402
And you can see even when you were creating this session, it tells you that this secret should be stored 403
in an environment variable so that it doesn't get added to your repository. 404
And that is what we're going to discuss in the next lesson, how we create environment variables, how 405
we prevent them from being exposed to other people, 406
because after all, this is an encryption key and it is very, very top secret. 407
So for all of that and more, I'll see you there.