Master the art of secure authentication with this comprehensive "JWT Authentication Tutorial for Node.js." In this video, you'll be guided through the process of implementing JSON Web Token (JWT) authentication in your Node.js applications. From setting up the Node.js environment to integrating the necessary dependencies and handling user authentication, this tutorial covers the essential steps to create a robust and secure authentication system. Dive into the world of token-based authentication, exploring how JWTs can enhance the security of your web applications. Whether you're a Node.js developer seeking to strengthen your authentication mechanisms or a learner eager to understand JWT authentication, this guide provides a clear and concise walkthrough. Elevate your backend development skills and ensure a secure user experience by implementing JWT authentication in your Node.js projects. Join this tutorial to unlock the potential of token-based security and enhance the authentication layer of your applications.
Show More Show Less View Video Transcript
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
#Business & Productivity Software
#Training & Certification
