0:00
It is finally here. This video we're going to be talking about JWT authentication using Node.js
0:06
and Express, and we're going to be breaking this into two separate parts. In the first part we're
0:11
just going to do simple JWT authentication where we create tokens, send tokens to users
0:17
and authenticate those tokens on our server. And in the second part we're going to talk about how
0:21
we can use a refresh token in order to automatically refresh our JWT tokens to increase the security
0:28
of our server, and also revoke privileges from users that we no longer want to access our server
0:33
similar to how a logout function works. So make sure you stick around to the end of the video
0:38
so you get both parts of this video. So with that out of the way, let's get started
0:45
Welcome back to WebDev Simplified. My name is Kyle, and my goal is to simplify the web for you
0:51
so you can start building your dream projects sooner. So if that sounds interesting
0:55
make sure to subscribe to the channel for more videos just like this one. And for this video
0:59
as always, I have Visual Studio Code opened up a completely blank folder so we can start this
1:04
project. And the very first thing we need to do is initialize this project with npm. So if we just
1:09
type npm init dash y, it's going to initialize our project with all the default settings
1:14
and now we can install the libraries that we're going to be using. The libraries we're going to
1:18
be using, as I mentioned in the intro, we're going to be using Express. So if you type npm i express
1:25
we also are going to be using a library called JSON Web Token, all one word. And then lastly
1:30
we're going to install dotenv. And this is just going to allow us to use a dotenv file, which is
1:36
going to contain our secret tokens for JWT. So we can just create that now dotenv. And this is where
1:42
we're going to put those secret tokens. Lastly, we're going to install a development dependency
1:47
npm i dash dash save dev. And this is called NodeMon. And what NodeMon lets us do is automatically
1:53
refresh our server every time we make changes to it. So we don't have to manually close and restart
1:58
our server ourselves. And as soon as that finishes downloading, we're going to create a script which
2:03
is going to allow us to start our server using NodeMon really easily. And we're going to do that
2:08
on a file called server dot JSON, this is going to be our main server file. So now inside our package
2:14
dot JSON, you can see all of our dependencies are showing up in here. And we can just come down here
2:18
and type in dev start. And we want this command to be NodeMon. And it's going to run our server
2:25
dot JSON file. So this is going to run our server dot JSON using NodeMon. And we can start that by
2:30
just running npm run dev start. And it's going to run that file for us. And of course, we're getting
2:36
an error because I forgot to save my package dot JSON. Now let's run that again. And as you can see
2:40
it started up our server. And since we have nothing in it, it just exited out, which is fine
2:45
Now as we make changes, for example, if we put a console dot log, put high in here, save it
2:49
you can see that it's actually refreshing our server every time. So to get started with our
2:54
server, we want to first import Express, we can just do that by requiring the Express library
3:00
And in order to set up an Express server, we need to get the app variable, which is going to come
3:04
from Express. So we can just call the Express function, and then say app dot listen and tell
3:09
it we want to listen, for example, on port 3000. Now we have an application running on port 3000
3:15
But of course, it doesn't do anything yet. In order to get our application up and running
3:19
let's actually just create a simple route and app dot get route. And this is going to get all of the
3:24
post inside of our application. And this is of course going to have a request and a response
3:30
variable, whoops, response. And inside of here, we can just run a function. And inside of this
3:38
function, all we want to do is return a list of posts. So let's just create a variable, which is
3:42
going to have all of our posts inside of it, just like this. And this is going to be an array with
3:47
objects, we're going to have a username for the person that created the post. So for example, me
3:52
and we're going to have a title for that, we can just say post one. And let's just put two
3:56
posts in here for now. So if I just copy this down, and we're going to change this username
4:02
here to be Jim. There we go. And of course, the title should be post two. And inside of our app
4:09
dot get, we can just return that. So we can say res dot JSON. And we want to return our post
4:14
Now if we save that, create a new file called request dot rest, whoops, dot rest, not reset
4:22
let me change that real quick. There we go. And this is going to allow us to make a rest request
4:27
API request to our API, you can use something else like Postman. But because I have the extension
4:33
installed in Visual Studio code called the rest client down here, it allows me to just use a dot
4:37
rest file to make these calls. And this dot rest file is really simple, we can just say we want to
4:41
make a get request to HTTP, localhost, port 3000. And we just want slash post. So now if we send
4:50
that request, you can see we get the list of our post back for us. So we know that our application
4:55
is working and running properly. Now the next thing for us to do is to actually authenticate
4:59
our request using JWT so that we don't let everyone access the post and only specific users
5:06
So we can just create a route to do that we can say app dot get. And this is going to be our login
5:10
route. And of course, it's going to take a request and a response. And the very first thing you do
5:16
in this login route is you want to authenticate the user. And in our case, I've already done a
5:21
video on user authentication using passwords and usernames. So if you want to check that out
5:26
it's going to be in the cards and the description below. So we're just going to ignore this portion
5:30
of the authentication. So we can focus purely on how JWT works in the authentication process
5:36
So the next thing we need to do is actually create that JSON web token. And to do that
5:41
we want to require that library we used earlier. So we can say const, and we want just JWT
5:46
which is equal to require of JSON web token. Also, since we're going to be passing JSON to this app
5:54
dot get, we need to make sure our server can handle that. So we can say app dot use, and we want to
5:59
pass an express dot JSON. This just lets our application actually use JSON from the body that
6:05
gets passed up to it inside of request. And we're going to change this to be a post actually instead
6:10
of a get since we actually want to create a token. So post makes more sense than get
6:15
Now inside of here, we want to get the username. So we can say const username is equal to request
6:20
dot body dot username. And now normally, you would make sure you authenticate the user first. But as
6:25
I mentioned, that's going to be in a separate video, which I've already done and you can check out. Now that we know this user has access to our application and is passed the correct username and
6:33
password, we want to authenticate and serialize this user with JSON web tokens. Now if you're
6:39
not already familiar with JSON web tokens and how they work, I have an entire video talking about
6:44
you should use JSON web tokens and how they work. So you can check that out again, linked in the
6:48
cards and the description below. For this purpose of the video, we're just going to worry about the
6:52
implementation of JSON web tokens. And it's really actually quite easy to create a JSON web token
6:57
we just use that JWT library, and we pass it dot sign. And the sign is first going to take our
7:03
payload, which is essentially what we want to serialize. And we want to serialize a user
7:07
object. So let's just create a user. And this user is going to be equal to here just an object which
7:12
has name and we want to pass that as our username. And now we can say that we want to serialize our
7:17
user. And in order to serialize them, we need some kind of secret key, this is going to be the next
7:22
parameter. And we're going to get this from our environment variables. So process dot ENV dot
7:28
access, whoops, access, token secret, just like that. And we can create this in our dot ENV
7:35
we can say access, token, secret. And we need to set this to some form of secret value. And
7:42
a really easy way to create a secret value is using the crypt library inside of Node.js. So
7:47
what we can do is just open up another terminal. And if we just run node here, we can just run
7:52
any Node.js code we want to, and we can require the crypto library. And all we want to do is
7:59
we want to get some random bytes. So we can say random bytes, let's just say that we want to get
8:04
64 of them. And then we want to convert this to a string, which is going to be an hexadecimal
8:10
Now we hit enter, this is just going to give us a random list of characters. If we run it again
8:14
you see we get a completely different random list of characters, we can just copy this up
8:18
as our access token secret. And since we're going to be doing refresh tokens later in this video
8:23
let's set up our secret for our refresh token as well. We'll just use this other value so that
8:27
these have two different secrets. That's really all that you need to make sure is that the secrets
8:31
for both of these are completely different. Now we can save that and go back into our JWT sign
8:37
And right now this is complete, we could add an expiration date to our token. But since we don't
8:42
have any way to refresh our tokens yet, we don't want to add any form of expiration date. So we can
8:47
just say that this is going to be our access token. And what we want to do is just return that so we
8:52
can say res dot JSON. And we want to pass down our access token as our JSON. So now when we make a
8:59
request to log in, whatever username we pass up, assuming it's authenticated correctly, it's going
9:04
to create an access token for us. And that access token is going to have the user information saved
9:09
inside of it. That's what we're doing with our sign here. So let's go over and actually test this
9:14
If we just put in here three hashtags like that, anything that comes after it is going to be a
9:17
different request. So we can just say post, we want to post to this HTTP localhost 3000. And
9:24
this time, we want to do a login. And we want our content type here to be JSON. So we can just say
9:30
application slash JSON. And then in our body, we need to pass up that username. So let's type in
9:36
username. And let's just say we want to do Kyle, for example. Now if we send that request, you can
9:42
see we're getting an error secret or private key must have a value. And this is because we're never
9:47
actually loading our dot ENV variables into our process dot ENV variable here. And we can actually
9:54
do that really easily just at the very top of our application, we just want to put require
9:58
And we want to put dot ENV dot config. Now if we save that and make a request, this should work
10:05
And there you go, you can see we have an access token being returned to us that has no expiration
10:09
date, though it has the user information saved in it. So we can access any of our endpoints with
10:14
this user information here. So now in order to test that, let's actually create some middleware
10:20
which we're going to put on our post here, which is going to authenticate our token
10:24
So we can take a function here, we're going to call it authenticate, whoops, authenticate token
10:30
And of course, since it's a middleware, it's going to take request response and next
10:34
And then inside of this function, all we need to do is we want to get the token that they send us
10:38
we want to verify that this is the correct user. And then we want to return that user up into the
10:43
function here for get post. So we can call that authenticate token function as our middleware
10:48
So we know we have that in our post. And now inside of this function, we need to get the token
10:53
And this token is going to come from the header. And we're going to have a header called bear
10:57
So it's going to say bear, and then it's going to have our token afterwards. So this string over
11:02
here, our token is going to come after the keyword bear. And that's going to be in our
11:06
authorization header. So in order to get this header, we can just say const off header is going
11:11
to be equal to request, and we want to get the headers. And then from that headers, we actually
11:16
want to get the authorization header, which is going to have the format of bear followed by our
11:21
token. So then all we want to do is get our token portion. So what we need to do is say that our
11:26
token is going to be equal to auth header, whoops, auth header, just like that. And we want to get
11:34
this split. So we're going to split it here on a space, because as you know, there's a space between
11:39
bear and token. And we want to get the second parameter in that array. So we just say one over
11:44
here. And this is going to be this token portion of our bear token. And in order to make sure that
11:48
we actually have a header, we first need to do auth header. And, and essentially what this is
11:55
saying is, if we have an auth header, then return the auth header token portion, which we split on
12:00
the space, otherwise, just return undefined. So our token is either going to be undefined, or it's
12:05
going to be the actual token. So we can do a check to make sure to see if our token is null
12:10
So if our token is null, we just want to return to the user an error code saying that they don't
12:15
have access. So we can say send status, and we want to send a 401 status code so that we know
12:21
that they have not sent a token to us. Now if we get to the portion after this token check
12:26
we know that we have a valid token. So we need to verify that token. We can do that with jwt
12:31
verify, and we want to pass it the token as well as the secret that we actually hash that token
12:37
with. So we can just say process dot env dot access token secret. And this is going to take
12:43
a callback, which has an error, and it's going to have the value we serialized, which in our case
12:47
is this user object. So it's going to have our user as well as an error. And the first thing we
12:52
want to do, of course, is check if we have an error. And if we do, we want to return another
12:56
status code to the user saying they don't have access. So we're going to send a status, and this
13:01
is going to be a 403, which essentially says that we see that you have a token, but this token is
13:05
no longer valid, so you do not have access. Now if we get past this, we know that we have a valid
13:11
token. So we can set our user on our request, we can say request that user is going to be equal to
13:16
this user object. So we know we have a user and we can just call next just like that. So that we can
13:21
actually move on from our middleware. Now if we save that, make sure that this is next instead
13:26
of next, I accidentally misspelled that up here, we now have access to our user, we can say request
13:31
that user to get our user. So let's actually filter the list of posts, we can say dot filter
13:36
And we want this of course to be a post. And all we want to do is get the post where the username
13:43
is equal to request dot user dot name. So this is only going to return the post that the user has
13:49
access to. So if we log in as Kyle, we'll get post one, if we log in as Jim, we'll get post two
13:53
And if we log in as anyone else, we won't get any posts at all. So let's test that. All we need to
13:58
do is send a request to log in, we're going to get an access token, let's just copy that. And up here
14:03
instead of our post, we want to send along the authorization header. And as you remember, it
14:08
goes bearer, and then we paste our token after that. Now if we save that and request our post
14:13
you see we're only getting the post for Kyle because we've logged in as Kyle. Let's log in
14:17
as Jim, send that request, copy over Jim's access token, and paste this in here. And we should get
14:23
back just Jim's post. So now we know that our authorization and authentication using JSON web
14:29
tokens is working properly, it's saving our user and everything's great. In order to further
14:34
emphasize the power of JSON web tokens, and how you can use them across many different servers
14:39
let's just go into our package dot JSON and create a dev start script to here. And we want to start
14:45
up server to dot JSON, which we're just going to copy this server dot JSON, rename this to server
14:51
to dot j s. And all we're going to do down here is change our listen to be on port 4000. So now
14:58
let's start that up, we can just say npm run dev start to hit enter. And now we have two different
15:05
servers. Whoops, make sure I save my package JSON, rerun that. And now we're have two different
15:10
servers run on port 3000. And one on port 4000. And the key thing is they both share the same
15:16
access token secret. So what we can do is inside a request, we can actually log in on our port 3000
15:22
As you can see here, our login works correctly, copy over this token. We can use this on our other
15:28
server at port 4000. And if we send a request, it still works. This is something that you can't do
15:34
very well when you use session based authentication, because your session is saved on that particular
15:39
server. But with JWT, all the information is in the token. So we can actually use it across multiple
15:44
different servers. So what we're going to do next is implement refresh tokens, which allow us to
15:49
actually take our authentication server and move it outside of our other third server. So we can
15:55
have one server which handles all of our creation of tokens, deletion of tokens and refreshing of
16:00
tokens. And another server which handles only our specific use case of getting post saving post all
16:06
of our different API related tasks, but not authentication. So in order to do that, let's
16:11
rename server two here to auth server. So we know that this server auth server is only going to be
16:16
for authentication. And our normal server is going to be for everything that's not authentication
16:21
So what we can do immediately is remove our login section from the server. And there we go. Now our
16:27
normal server, all it has is the get for post, and we can authenticate our token, but it has nothing
16:33
else inside of it, we can just save that. And our package dot JSON here, we want to change our dev
16:38
start to be dev start off. So we have our normal dev start, and then our auth one, which is going
16:43
to be starting our auth server dot j s. And of course, let's just cancel out of this and make
16:48
sure we run our dev start off. And now it should start up our auth server. And our other server is
16:55
already running on a different port. So we have our server running port 3000. And now our auth
17:00
server is running on port 4000 here. And then our auth server, we can actually remove all this post
17:06
related code, because we're no longer going to be allowing post on this server, it's only going to
17:10
be log in, log out and refresh tokens. So before we start building and implementing our refresh
17:15
tokens, I need to talk a little bit about why we need a refresh token. Right now when we create a
17:20
token, it has no expiration date, which means anyone with access to that token will have forever
17:25
access to that user's account. And they can just constantly make requests as if they were that user
17:31
as long as they have that access token. It's very difficult to secure that because they just have
17:35
infinite forever access, it'd be like giving up your API key when you're accessing API, the user
17:41
just now has access forever. The idea of a refresh token is that you save the refresh token in a safe
17:47
spot. And then your normal access tokens here have a very short expiration date. So that if someone
17:52
gets access to your access token, they only have access to your account for maybe a few minutes
17:57
before the access is revoked. And then the user must use the refresh token to get a new token
18:02
And now this does have the same problems where your token could get stolen, and someone else
18:06
could use that token to refresh your token. But that's where the idea of invalidating a refresh
18:11
token is, you can essentially create a logout route, which deletes a refresh token so that
18:16
the user now can no longer use that refresh token, essentially, it removes it from the list
18:20
of valid refresh tokens. So really, the main reason to use a refresh token is so that you
18:25
can invalidate users that steal access that shouldn't have access. And the second reason
18:30
is so that you can take all your authentication and authorization code and move it away from your
18:35
normal server. As you can see, we have two different servers. And one is specifically
18:39
for authorization. So you can scale these servers separately. If you have a lot of authorization
18:44
you can make your authorization server bigger without having to make your other servers bigger
18:48
as well. It's really great for when you want to deal with micro architectures or things like that
18:52
So the first thing we need to do is let's just create a function which creates an access token
18:56
for us. And we also don't need this function for authenticating. So let's just create over top of
19:01
this a function which is going to generate access token, and we're going to pass it the user
19:07
we're going to generate this token for this code is going to be very similar to this code right
19:11
here. Let's copy this down. We want to make sure we return this. And the only extra thing we need
19:17
to do is add in an expiration date. So we can just say expires in. And for our use case, we can put
19:23
anything for example, 10 minutes, usually you're going to have some short minute range, maybe 1015
19:28
minutes, we're going to just set this to 15 seconds, just so I can easily show you how the tokens
19:33
expire and how you can refresh them. But again, you would want to make this much longer in a real
19:37
application, something like 1015 minutes, maybe 30 minutes, whatever works well for your use case
19:43
Now that we have that done, let's just change this code up here to call that function to generate our
19:47
access token. And now everything's working fine. But our token is going to expire in 15 seconds
19:53
So we need to create a refresh token. Let's just do that right here, we can create a refresh token
19:58
And this is just going to be equal to JWT dot sign. And we want to sign a user, we essentially
20:04
want to put the same user inside of both tokens. So we can easily create a new token from our
20:09
refresh token, which uses the same user. We also want to go to the environment variables, and we
20:14
want to get our refresh token secret, so that we can serialize this. And we don't want to put an
20:20
expiration date on our refresh token, we're going to manually handle the expiration of these refresh
20:25
tokens. And we don't want JWT to do that for us. Now the last thing to do is to return this refresh
20:31
token to our user just like that. And now if we go over to our request, and we make a post on local
20:37
host, this is going to be 4000 to log in. If we send that request, you can see we now get an access
20:42
token and a refresh token back. And let's make sure we change this back to 3000 for post. And if
20:47
we try to make a request, this is most likely going to fail since I don't think I got it quick enough
20:51
Okay, I did. So it hasn't expired yet. But if we keep sending requests, you can see after 15 seconds
20:56
we're now forbidden from accessing this route. And we need to use our refresh token to create a new
21:01
one. So now let's go over to our off server. And we need to create a new function, this is going to
21:06
be app dot post, and we want to post token. So essentially, this is going to be for creating a
21:10
new token, it's going to be a request, and a response. And inside of here, we're going to take
21:15
in a refresh token. So we can get a refresh token, this is going to be equal to request dot body dot
21:21
token. And this request is going to look just like this, let's actually just create a really sample
21:26
request, we're going to post to HTTP slash slash localhost 4000. We want to post to token
21:35
And we're going to of course, make sure our content type is going to be set to JSON
21:40
And inside of our JSON, we're going to pass a token. And that token is going to be our refresh
21:45
token. Right now, we don't have one. But if we were to make a request here, for example, here
21:48
is a refresh token we can use. And we could paste that in there. And this would pass up that refresh
21:52
token to our off server here. And we need to use that refresh token and check to see if we already
21:58
have a refresh token that exists for that. So normally, you'd want to store your refresh tokens
22:03
in some form of database or some form of Redis cache. But for our use case, we're just going to
22:08
store them locally in a variable, we're just going to call this refresh tokens. And we're going to
22:13
set this to an empty array, going to initialize this with let just like that. And now this is not
22:18
a good idea to do in production, because every time your server restarts, this is going to be
22:23
emptied out. But it's a lot easier to show you and demonstrate with than creating an entire database
22:28
just to store these tokens. And the first thing we need to do is every time we create a token
22:32
just like down here, we just want to set our refresh tokens dot push, and we want to push
22:37
in that new refresh token we just created. So now we have a record of that refresh token
22:42
And all we can do inside of this token is check if that token actually exists. So first, we want
22:46
to say if refresh token is equal to null, we want to return our status down here. So we can say res
22:52
dot send, whoops, send status, let me just close out of the section over here, this is going to
22:59
be a 401 status. Now the next thing we can do, whoops, I spell things correctly is we can check
23:06
if our current refresh tokens includes that refresh token that we got sent to us. So what
23:12
this is saying is, do we have a valid refresh token that exists for this refresh? Essentially
23:17
does this refresh token still valid? Have we removed it? Or is it still good? And if it does
23:22
not exist, what we want to do is again return saying that they do not have access. So we're
23:26
going to send a status, this time saying 403. Now if they get all the way past all those checks
23:32
we can actually verify this refresh token. So we can just say refresh token
23:37
we want to get that secret variable. So process dot env dot refresh token secret. And of course
23:43
we're going to have an error as well as our user object being returned inside of this callback
23:47
function. And of course, we want to check our error first. And if we have an error, we can just
23:52
return that error. So we can say return. And we want to send down a status of 403, very similar
23:57
to our normal authentication. But now what we can do is actually create our access token, we can say
24:02
const access token is going to be equal to generate access token. And you would think we can just pass
24:08
this user object. This user object actually contains additional information such as the
24:13
issued at date of our token. So we actually need to get just the name. So we can say we want to get
24:18
the name and the user dot name that we were passing down just this raw user object and not
24:23
all that other additional information. Now we can return that information by just sending res dot
24:28
JSON of our access token, just like that. And now we're finally ready to test this. So if we go over
24:35
here, and we log in Jim, you can see we get both a refresh token and an access token. If we copy
24:41
our refresh token, paste it into our token function, and we copy our access token and paste
24:46
it up here, we can send some requests and you can see it's working and it's working but eventually
24:51
we're going to get the forbidden status, we no longer have access. So let's create a brand new
24:55
access token, we can copy that access token, paste it up here. And now we have access again for
25:01
another 15 seconds. And eventually we're going to lose access. And you even notice that these
25:05
are on different servers. This is port 3000. And these are in port 4000. So our authentication
25:10
where we log in and create refresh tokens and handle refreshing our tokens all happens on a
25:15
different server than our actual API, which is great. Now the next thing I want to talk about
25:20
is how to actually de authenticate refresh tokens. Because right now, for ever and ever and ever
25:25
users just click the send request button to create infinite access tokens for users, no matter what
25:31
And as long as they have that refresh token, they can do that. So in order to prevent this, we need
25:35
to have some form of delete function. We're just going to call this a delete here. And we want to
25:40
do for example, log out. And what this is going to allow us to do is actually delete those refresh
25:45
tokens. And this is really easy. Normally, you'd have to delete them from some database. But since
25:50
we just are storing them in a variable here called refresh tokens, we can just say we want to set our
25:55
refresh tokens equal to a filtered version of our refresh tokens, where all we're doing is we're
26:01
just checking, let me just close out of this here, we're just checking to make sure the token that is
26:06
inside our refresh tokens is not equal to our request dot body dot token that we pass up to it
26:13
And then all we want to do is just send a status. And we're just going to send 204 saying that we've
26:18
successfully deleted this token. Let's save that and actually make a request for that
26:24
So we can just say delete. And we want to go to local whoops, localhost 4000. We want this to be
26:33
logout. We want our content type to be JSON, of course. And inside of here, we need to pass that
26:41
token. So let's actually do another generation here where we're going to log in our user
26:46
you can see we get an access token, and we get a refresh token. Let's copy this refresh token
26:51
over into this section. And we also want to copy it into this section for deleting
26:55
So we can actually refresh this token. As you can see, we're getting new access tokens, and everything
27:00
is working. But if we click to delete, you see we get to a four means they successfully executed
27:05
And now we try to actually create a new refresh token or access token, you can see it no longer
27:10
works. And that's because we've removed it from this list. So the user no longer has access to it
27:14
when they try to create a new token, and it exits out right here when it's checking to make sure
27:18
that refresh token already exists. And that's all there is to implementing JWT authentication in Node
27:24
JS and Express. If you enjoyed this video, make sure to check out my other videos linked over here
27:30
and subscribe to the channel for more videos just like this one. Thank you very much for watching
27:34
and have a good day. Transcribed by https://otter.ai