Transcripts
1. Class Introduction: Welcome back to the
Express Jazz course, Model nine, authentication
and authorization. My name is ShenRagunhi, and I will be your instructor
for this exciting module. Over the years, I have had the privilege of
building secure, scalable backend systems for
a variety of applications, and I'm here to share
my expertise with you. One of my best projects
involved designing a user authentication system
for an e commerce platform, which significantly improved
its security and usability. Today, you will learn to build something
just as impactful. In this module, we are diving deep into authentication
and authorization, two critical aspects of
modern web development. So you'll learn how to register users securely using
hashed passwords, authenticate users
with JCN webtkens, protect sensitive
routes with middleware, and finally, implement
role based authorization to manage user permissions. This knowledge is crucial for securing any
backend application, and by the end of this module, you will be confident in implementing these
features in your projects. So this module is designed
for Bg in developers who want to take their Express JS applications to the next level. If you have completed
the earlier modules, you're all set to start. Basic knowledge of JavaScript, no JS, and Monger
DB is recommended. Authentication and
authorization are cornerstones of any
secure web application. By mastering these concepts, you will not only build
safer applications, but also increase your
value as a developer. These are must have
skills for creating user centric platforms
in today's tech world. And finally, for the project, you will integrate
everything you have learned into the
Fair Wheels app. So more specifically,
you will implement user registration and
login functionality, securely authenticate users
using JCN web tokens. Apply role based authorization to protect routes and resources. By the end, you will have a secure and scalable
user management system for the Fair Wheels app. This module is a game changer for your Bend
development journey. So let's build secure
applications together. I'll see you in the first
lecture. Let's get started.
2. Authentication and Authorization - An Introduction: All right, so back to our
Fair Wheels application. So far, we have built
these API endpoints. So we can manage companies, cars, customers, and rentals. Now, nearly all
applications out there require some kind of
authentication and authorization. So in this section, we're going to take this
application to the next level and implement authentication
and authorization. So before we go any further, I want to make sure that
we are on the same page. So authentication
is the process of identifying if the user is
who they claim they are. That's when we login. So we send our user and
password to the server, and the server authenticates us. Authorization is determining if the user has the
right permission to perform the given operation. So in our Fair
Wheels application, we want to make sure that
only authenticated users or only logged in users can perform operations
that modify data. So if the user is anonymous, if they are not logged in, they can only read data
from these endpoints. If they want to create a new
company or update a car, they have to be
authenticated first. Now as an additional security, we want to make sure that only admin users
can delete data. So that's a second
level of authorization. We're talking about
permissions here. So these are the requirements we are going to implement
in this section. So to do this, we need to add two new endpoints
to our application. First, we should be
able to register users. For that, we are going to
send a post request to slash API slash USERS because we post, we create new resources. In this case, a new user. We should also be able
to log in a user, and that's used for
authentication. Now, here's a question for you. What SDDP method should we
use to implement login? Because with login, we are
not creating a new resource. We are not updating or
removing an existing one. So how can we implement
this in restful terms? This is one of those
scenarios that you may encounter frequently in
real world applications. Sometimes the operation you are dealing with doesn't
have that create, read, update, delete semantic. The way we model this
in restful terms is by referring to this
as request or command. So you're creating a new login request or
a login command. In that case, we will use post because we are
creating a new resource, so slash API slash Logins. Now in your application, maybe you want to store
all the logins into the application in a separate
collection in Mongo Deb. So you can see using post
makes perfect sense here. But even if you don't store individual logins and you just want to validate
the user and password, you can still treat
this resource as login resource and use
post to create it. Now, here's an exercise for you. I want you to implement this
API to register new users. So for each user, we want to have
these properties, name, email and password. Also, when defining
your schema for the email property in
the schema type object, set the unique property to true. So when we define the schema, we set the type of email to an object that's our
schema type object. We set the type here to string and also set unique to true. So with this, we ensure
that we will not store two documents with the
same email in Mongo Di B. Okay. So go ahead and implement only this API
to register new users. I'm going to do the same
in the next lecture.
3. Creating the User Model: All right. So first, I'm going to define
a new user model. So here in the models folder
at a new file user dot js. Now to save time,
I'm going to go to company dot js and borrow
some code from here. So copy back touserdtjs,
paste it here. Now on the top, so
we have this schema. I'm going to define this while
calling the model method. There is really no need in this case to define this
as a separate constant. So let's get this schema
object and add it here. And now we can get
rid of this schema. Okay, that's better. So that's our model. We should call this
constant user. And the corresponding collection
should also be users. Now here, we have name
property, which is a string. It's required, and it's 5-50 characters. That
sounds good to me. Next, let's add email. So I'm going to grab all
this good, duplicate it. And the second
property is email. So again, we have
these properties. I would like to increase
the maximum length to 255 characters. And also, as I told you
in the last lecture, we should add the unique
property to make sure we don't store two users with
the same email in Manga Divi. And the last property is Passwb. So I'm going to copy all this, duplicate and add this
password property. Now I'm going to set the
max length of passwords to a higher value because we are going to hash
these passwords. And here we don't need
the unique property. Next is our validate function. So we need to rename
this to validate user. Validate user that
takes a user parameter. Here we have name which is 5-50 characters,
and it's required. We have email, which should
be 5-255 characters. It should be required.
And here we also call the email method to make
sure it's a valid email. And finally, we have password, which is also a string with minimum five characters
and maximum, let's say, 255 as required. So this is the password that the user sends in plain text. We're going to hash this, which is going to
be a longer string, and that's the string we
will store in Mongo TV. So we are done with our
valid user function. Now we need to export
our user model, so we don't need the
first export statement that's for our schema delete. Here we're going to
export this as user, and our validate
function is also good. Beautiful. So we are done
with our user model. In the next lecture,
I'm going to add a route to register new users.
4. Registering Users: All right. Now we are going to create a new route to
register new users. So here in the routes folder, I'm going to add a new
file users dot js. Now, once again, to save time, I'm going to go to
companies dot js and copy these required
statements as well as the second route
into our new module. So copy, paste it here. Again, copy the post route. And paste here. And by the way, I'm only doing this because
I don't want to waste your time watching me
type all this by hand. In the real world,
you should avoid copy paste approach unless
you are very careful and you are going to read
every single line of the copied code to make sure
you didn't make a mistake. It's always better to
type the code by hand. Now, let's make changes. So on the top, we need to import the user
model instead of company. So from models user, we import the user class, as well as the
validate function. We also need Express and Router. So here's our new route which is posed for creating new users. And finally, we need
to export this router. So module dot Exports. We set this to this router. Now, we need to go back to our index dot JS
and tell Express that for any routes that
starts with slash ABS SRS, we should use this router
that we're exporting here. So let's go to Index or Js. On the top, let's
import this new module. So constant users. We set this to require from
the routes folder users. And then here we call
app that give our path slash APAs Users and our
Router, that is users. Okay, so we have built
the big picture. Now, let's go back to our users module and implement
this new route. So here we are going to
validate the request. If it's not valid, we
are going to return a 400 error, which
is bad request. Otherwise, we are
going to create a new user object and
save it to the database. So we are going to keep the first two lines
exactly as they are. We want to validate the request. We are using our Joy
validate function. So if the name, email, or password properties
are not valid, we are going to return a
400 error to the client. Now, next, we need to do
another kind of validation. You want to make sure that this user is not
already registered. So we call user dot Fine one, pass a query object here. We are looking for user with a given email that is
request dot body dot email. So note that here, I didn't use fine by
ID because we are not looking up the
user by their ID. We are looking them up by
one of their properties. So fine one. Now this
returns a promise. So we await it and
get a user object. Now, in this case, I'm
defining a variable instead of a constant because
we are going to reset this as you
will see in a second. So in this case, if we do have this user
object in the database, we should return a bad
request error to the client. So return response
dot status 400, which is bad request dot SEND and here's the message
user already registered. So at this point, we have
a valid user object. This user is not registered
in the database, so we need to save this
user in the database. So here, I'm going to reset this user object because at
this point, it should be no. We set this to a new user, and here we set the properties. So name we set this to
request dot body, dot name. Email to request
dot body dot email. And password to request
dot body password. So this is our user object. Now we save it. So await, user dot save, and finally, return
this to the client. So we don't need these two lines for working with the company. Delete. And finally, return this new user to the client.
Now, let's test this. So back in Postman, I'm going to send
a post request to Local Host port 3,000
slash APAS USERS. And then in the body
of the request, I'm going to set this to Raw
and set the type to JSON. Here we pass as JSON object
with three properties, name, Shiv, then we set email. I'm going to set
an invalid email, and I'm not going to set
the password either. I want to make sure our validation function
is working properly. So send. Okay, we
got a bad request. That's good. Name length must be at least five
characters long. So let me change this
to hewnder Alright. Now let's send another request. We got another bad request. Email length must be at
least five characters long. So let's change
this to 12, three, four, five, six, but this
is not a valid email. So now we should get
a different error. Email must be a valid email. Beautiful. So let's change this to test 123 at regmil.com. Et's send another request. Okay, now we get
password is required. Beautiful. Finally, let's add a password that is at least
five characters long. So one, two, three,
four, five, Send. This time we got a 200 response. Beautiful. And this is the user object that you
stored in the database. So you have the ID property as well as name, email
and password. Now, when registering
a new user, we don't want to return their
password to the client. So that's something
we are going to fix in the next lecture. But let's send another request with the exact same values. This time, we should
get a different error, telling us that the user
is already registered. So send. Okay, we got
another bad request, and here's the message. User already
registered. Beautiful. So in the next lecture, we're going to modify the
response of this API and point.
5. Using Lodash: So back in our post method, we want to modify the
response to the client. So there are two options here. One approach is to return
a custom object like this, setting the name
to user dot name and email to user dot email. So this way, we can exclude the password and the
version properties. This approach is perfectly
fine, but in this lecture, I'm going to introduce you
to a useful library that gives you a lot of
utility functions for working with objects. If you are an experienced
JavaScript developer, you will probably know what I'm talking about. That's Lourdes. So head over to lodash.com. This is lodash, which is basically the optimized
version of underscore. Underscore has been
around for a long time. It has a lot of utility engines
for working with objects, strings, arrays, and so on. So if you look in
the documentation, here we can see all
the utility functions that we have for working
with various types. We have lots of
utility functions for working with arrays, numbers, strings,
objects, and so on. So lodash is extremely powerful. And in this lecture,
I'm going to show you how to use it in
your node application. So back in the terminal,
NPM install lodash. So note that the current
version I'm using 4.17 0.21. All right now back in the code. So here in our user's
model on the top, I'm going to import Lodash. So require Lodash. Now by convention, we store the result in a constant
called underscore. You can call this anything, you can call this Lodash, but by convention, we use underscore because
that's short and clean. Now this underscore
object that we have here has a
utility method called pick so underscore dot PIC. We give this an object. In this case, our user object, and then pass an array
of properties in this user object that
we want to pick. And this will
return a new object with only those properties. So here in this array, I'm going to pass
name and email. So when we call
this pick method, we'll get a new object with these two properties,
name and email. Now instead of manually
repeating the user dot, we can use this Pi method. That's more elegant. So we can replace this object here with what we get
from the PI method. As simple as that.
Now, perhaps here, you may want to include
the IR property as well. So that's how we
use the PIC method. Similarly, we can
modify this code, and instead of repeating
request dot body dot, request dot body dot, request dot body dot
with underscore dot PIC. So I'm going to
replace this object with underscore dot pig. We gave it request dot py. Now here, we might
have 50 properties. A malicious user may send us properties to be
stored in the database. We only want to hand
pick a few of these. So we pass an array. We are only interested in
name, email and password. Okay, so we create
the user and save it and then return this
custom object to the client. Let's test this one more time to make sure
everything is working. So backend terminal. Let's run the application. Beautiful. Now back
in the Postman. I'm going to send a new request
through the server. Send. Okay, here's our
new user object. We only have ID,
name, and email. Now in all these
requests so far, I have sent really
simple passwords. If you want to enforce
password complexity, there is an NPM package
built on top of Joy called Joi
password Complexity. So back in Google search for
Joy password Complexity. That's the NPM package. So with this, you can
configure an object that determines the password complexity in your application. Minimum number of
characters, maximum, how many lower as or upper
case you want to have, how many numbers, and so on. So the name of the package
is Joy password complexity. In this course, we are not going to use this because that's something you can
do on your own. All right, our new API
endpoint is in good shape, but we are storing our
passwords as plain text, and this is very, very bad. So in the next lecture, I'm going to show you how
to hash these passwords.
6. Hashing Passwords: In this lecture,
I'm going to show you how to hash passwords. For that, we are going to use a very popular library
called B crypt. So here in the terminal, let's install B crypt. Install. Note the
version that I'm using. That's version 5.1 0.1. Now here, I'm going to
create a playground file, so you'll learn how to work
with this BCRP library, and then we are going to
take that code and put it in a route for hashing the
password of new users. So let's create a new
file, hash dot js. In this file, first, we need to load BCRP. This retains an object. We store it here. B crypt. Now to has a password, we need a salt. What is a salt? Well, imagine our password
is one, two, three, four. When we hash it, let's imagine we will get
a string like this. Now, this hashing
algorithm is one way. So if we have A, B, CD, we cannot decrypt this and
get one, two, three, four. So from a security point
of view, that's great. If a hacker looks
at our database, he or she cannot decrypt
these hashed passwords. However, they can
compile a list of popular passwords and hash them. And then they can look at the database of our application. They find this hash password, and they know that ABCD
represents one, two, three, four. So that's why we need a SALT. As SALT is basically a random string that is added before or
after this password. So the resulting hashed
password will be different. Each time based on
assault that is used. Okay, let me show
you this in action. So here we call B
crypt dot Gen SLT. Note that this method
has two versions. The first one is asynchronous. The second one is synchronous. As a best practice, you should always use asynchronous methods because as I told you at the
beginning of the course, in node applications, we
have a single thread. We don't want to keep
that thread busy because then we can't
serve other clients. So we call GenSLT
as an argument. We pass the number of frowns. We want to run this algorithm
to generate the SLT. Higher the number,
the longer it's going to take to
generate the salt. And also the SOT will be more complex and
harder to break. So the default value is ten. We're going to use that now because this is an
asynchronous method. We can either pass
callback here, and that's what you see in
their official documentation, as well as a lot of
tutorials on the web. But this method also has an overload that
returns a promise. So instead of
passing a callback, we get a promise, await it, and then get the salt. So now we need to wrap this in an acing function like run. Okay, so let's lock this
salt on the console. And finally, let's call
this run function. Now, back in the
terminal node has to Js. So this is an example of salt. You can see the number of rounds we used included in the salt. So if we use 20 here instead
of ten, we would have 20. So here we have a
long random string that is included as part
of hashing our passwords. And with this, every time we hash our password
with a new Salt, we get different results. So now that we have a Salt, we can use this to
hash our password. We have another method here on this B crypt object
that is hash. So we give it our data. Let's imagine our
password is one, two, three, four, and
give it the salt. And again, look, the third
argument can be a callback. We are not going to use this. Instead, we're going to get the promise that is
returned from this method. So we await that promise and
get the hashed password. So now let's log this
on the console as well. Hashed. Okay, back in the
terminal node, hashed or chase. Okay, look, on the first
line, we have our salt. On the second line, you can
see the salt here as well. So the SALT is included
in the hashed password. The reason this is included is because later when we want
to authenticate the user, we want to validate their
username and password. So there the user sends a
password in plain text. We need to hash it again, but we need to have
the original salt that was used to
generate this hash. So during comparing the
plain text password with the hashed password, B crypt needs to know the original salt that was
used to hash the password. So now that you know
how B Crypt works, let's take these two lines and put them in
our route handler. So cut them from here. Let's go to users dot js. Okay, here's our user object. Let's pase those lines here. So we generate a salt, and then we need to
has the password. Instead of one,
two, three, four, we're going to use
user dot password. That's plaintext password. So we hash it with a
salt and then reset it. So user dot password. Now on the top, we also
need to import B crypt, so constant B crypt. We set this to require Bcrypt. Now, here in Compass, I want to delete the
user's collection because all the users
we have so far, they have plain text password. So delete. All right. Now back in the terminal, let's run Node mod. And then here in Postman, we are going to
send a new request. S. Beautiful. So here we have a new user. Now back in Compass, let's refresh the list. We have the user's collection, and here's our new user
with a hashed password. Beautiful. So we have implemented this endpoint
to register new users. We are done here. Next, we are going to look at
authenticating users.
7. Authenticating Users: All right, so here in
the routes folder, let's add a new file. We call this logins
dojs or auth dot js. Either works, but
auth is more common. So auth Js. Again, you save time, I'm going to borrow some code. So let's go to users dot js. I'm going to copy all the code that we wrote in this section, and then paste in auth dot js. Now before we get
into the details, let's go to index dot JS. On the tap, import
this new module. So Cs OT, we set this
to require routes OT. So if we have any request
to this endpoint, slash API OT or one of
its child endpoints, we are going to delegate
this to the OT router. So the big picture is done. Now let's look at the details. So back in the Auth module, on the top, first, we need to validate the
body of the request. But this validate
function we have here, this is the one that we
imported from our user module. So this is validating that. In the body of the request, we have three properties, name, email and password. And in the real
world application, you might have other properties as part of registering a user. So this validate function is
for validating a new user. It's not for validating
the email and password that we expect
in this endpoint. So here we need a different
validate function. So I'm going to remove this
validate function from here and define a separate validate
function in this module. Now to save time,
I'm going to go to user module and copy
that validate function. So we have validate user. Let's copy this back
to the Auth module, paste it here, and then
change this to validate. Now we can change the
parameter to request. Now for this schema object, we need only two properties,
email and password. So let's delete the
name and we are done. So back to our route handler, this is our first validation. Next, we need to make sure that we do have a user
with a given email. So we load the user. If we don't have the user, so here we apply
the not operator. If we don't have the user, we should send a 400
error to the client, which means that request
and the message should be invalid email or password. Note that here, I'm not
sending a four or four, which means not found
because we don't want to tell the client why the
authentication fail. We don't want to
tell if this email is correct or the password. So we don't want to say we don't have a user
with a given email. We just tell the client
that this is a bad request. It doesn't have the right
data to be processed. So this is for validating the username and
email in this case. Next, we need to
validate the password. For that, we need to use BCRP. So let's delete all this code. This B crypt object that we
have has a compare method. We use this to compare a plain text password
with a hashed password. So our plain text password is in request dot
body dot password, and our hash password is
in user that password. So as you saw earlier, this hash password
does include the SLT. So when we call the
compare method, B crypt is going
to get that salt and use that to rest this
plain text password. If they are equal, then
this will return true. So here we need to await this promise and store the
result here in valid password. Now, if the password
is not valid, again, we are going to
return a 400 error with that vague message, invalid email or password. So I'm just going to copy
this and paste it here. And finally, if we get
this to this point, that means this
is a valid login. So for now, I just
want to send a simple, true value to the lint.
8. Testing the Authentication: So for now, let's test
this authentication route. Before that, so an of dot js. Here, I forgot to
mention load joy. So let me quickly do this. Last Joy, set this
to require Joy. Okay. And at the bottom
for validate function, here we need to return
schema dot validate request. Okay, so we're all set. Let's open postmin. So here I'm going
to open a new tab. Let's select post here. Let me type the endpoint. It's TP, local host OT, API OT. And in the body of request, I'm going to send the GSN out. So let me select Json here. And now we have two parameters. So first one is email. The email that we
have used is a 23, four, five, six, seven, at the rate gmail.com. And our password is one, two, three, four, five. So let me send this request, SN. Beautiful. We have
a status 200. Okay. And here's our response through. Now, let me change
the password here to something else like 67 and
then send the request again. See, we have a 400 bad request
and the generic command, which is invalid
email or password. Now let us change the email to something else with
the correct password. So something like this. And let me send the
request again. So send. See, again, we have
a 400 bad request and the generic message which is invalid
email or password. So our authentication
endpoint is working fine. In the next lecture, we're going to see the response
that we are sending here and change it to something what we call
adjacent web token.
9. JSON Web Tokens: So we have an endpoint
for authenticating users. Now, we need to modify
the response here, and instead of
returning a true value, we need to return
a JCN web token. AJCN webtoken is basically a long string that
identifies a user. As a metaphor, you
can think of it as your driver's license
or your passport. It's very similar to that. So when the user logs
in on the server, we rod this JCN web tooken, which is like a driver's
license or passport, we give it to the client
and then tell them, Hey, next time you want
to come back here and call one of
our API endpoints, you need to show your passport. You need to show your driver's
license. This is your ID. So on the client, we need to
store this JCN web token, which is a long string, so we can send it back to the server for future API calls. Now, the client can be a web application or
a mobile application. If it's a web application, if you're building an application
with angular or react, you can use local storage. That is a special
storage place that is available in every
browser out there. If you're building a mobile app, you have a similar option depending on what
platform you use. So now let me show you an
example of AJCNwbTken. Head over tojwt dot IO. On this website, here we have a debugger for working
with JS and WebTkens. So here in the encoded section, you can see a real example
of AJCN Web tooken. This long string that you see in the encoded
section represents a JCNObr so when we decode this, we'll get a CN object. Okay? Now we can see that
this string has three parts, and each part is color code. So the first part is red, the second part is purple, and the third part is blue. On the right side, you can
see this string decoded. So the red part is what we call the header of
a JCN web token. In this header, we
have two properties. One is L, which is
short for algorithm that determines the algorithm used for encoding this token. The type is GWT, which is JCN web token. Now we never have to
worry about this header. It's just a standard. What matters to us
is the second part, the payload, which
is the purple part. So here we have a JCN object
with three properties, sub, which is like a user
ID, name, and Admin. Now, the payload that you see is different from
the payload I have because I have modified the fold JS and webtog and
it is on this website. So I generated a custom
web token, put it here. What I want to point
out here is that this payload includes public
properties about the user. Just like on your passport, you have some properties
about yourself, like your name,
your date of birth, your place of births, and so. So we have the exact same
concept ren adjacent web token. Can include a few basic public
properties about the user. And with this, every time we send a token from the
client to the server, we can easily extract the
user ID from the payload. If we need to know
the name of the user, then we can simply extract
that here as well. We don't have to
query the database, send this ID to
get a user object and then extract
the name property. By the same token,
if you want to know if the user is an
admin user or not, we can include that here. So again, we don't have
to send an extra query to the database to see if the user with a given
ID is admin or not. Now you might be concerned about this approach from a
security point of view, because you may think
anyone can simply set this admin property
for themselves to true, and then they will be treated
as admin on the server. But that's not how
Jason ptocans work. The third part of
this JCN Web token, which is in blue, is
a digital signature. This digital signature is
created based on the content of this JCN WebTken along with
a secret or private key. The secret or private key is only available on the server. So if a malicious user gets the JCN WebTken and modify
the admin property, the digital signature
will be invalid because the content of the
JCN WebTken is modified. Now we need a new
digital signature, but the hacker cannot generate this digital signature because they will need the private key, which is only available
on the server. So if they don't have
access to the server, they cannot create a
valid digital signature. And when they send this tempered JCN Web
token to the server, the server will decline that. The server will say, this is
not a valid JCN web token. So this is how JCN
Web tokens work.
10. Generating Authentication Tokens: So on this page, if you
look at the libraries, here you'll find
different modules or libraries for
different platforms. So if you scroll down,
you will see you have Tad net one C B, C, C plus plus, node, and so on. So let's open the terminal
and install JCN webtook and BM and install ASN web took. Okay, if you look at
the package a JSON, you will see the version that
we are using is 9.0 0.2. So let's close this. We have the path for
authenticating users. Now, we need to create a JCN webTken before sending a
response to the client. So before that, you need
to load JCN WebTken. So on the top,
require Jason rep, choke and store it in
a constant called JWT. Great. Now here, we'll use
sine method of JWT. So JW dot sine so it will
accept two arguments. First one is the payload and the second one is the
secret or private king. So the payload could
be a simple string or it could be an object like
the one that you see here. So let's make our object
with only one property. We will include underscore ID, and we will send this to
user dot underscore ID. Now as per the second argument, we will send this to a string. But ideally, you
should not store your secret or private
key in your source code. Later, I will show you how to store this in an
environment where o. But for now, let's just
put this to JWT secret. It doesn't need to
be JWT secret key. It could be anything,
any string. But for now, let's use this. So we have used a sign method, and as a result, we get a token. So let's store it in constant
and we'll call it token. And now we'll return or send the response
to our client as hook. So there's this back
to the terminal. Let's run down on. Now, let's go to postman. Here we will send the
correct email and password to this authentication
route. Then let's send this. And here's our JCN web token. So let me copy this. And let's go back
to the debugger. In this encoded section, you can just delete this one and paste the
one that we copy. And here, in the
decoded section, you can see our object
that we send as a token so here we
have the ID property, which is set to the object
ID in the Mongaim database. And in addition, we
have IAT which is the current time and the time on which this token is created. This is used to H the token. So we have successfully created the token and send it as
a response to our clime. In the next video,
I'll show you how to include this secret key in
an environment variable.
11. Storing Secrets in Environment Variables: Earlier in the course, when I was talking about
Express advanced topics, I introduced you to a node
package called Config. We use this package to store the configuration
settings of our application in JSON files
or environment variables. So in this lecture, we are going to take
this secret key out and store it in an environment variable
because as I told you before, you should never store your
secrets in your code base. Otherwise, these secrets are visible to anyone who has
access to your source. So back in the terminal, let's install the config module. I can note the version
number that's 3.3 0.11. Now first, we need to create
a new folder that is config. In this folder, we add a default configuration file
that is default dot JSM. So a simple SN object. We could have several
settings here. But for now, I just want
to add one setting. That is JWT secret key. Now in this default GSN file, we are going to set this
to an empty string. The actual value is not here. So here we are just defining a template for all the
settings in our application. Now we need to create
another file that is custom environment
variables dot JS. Note the spelling, make
sure to get it right. Otherwise, what I'm
going to show you in this lecture is not going
to work on your machine. So as I told you
before, in this file, we specified the mapping between our application settings
and environment variables. So I'm going to go back
to our default dot Son, copy all this, paste it here. So this is the structure of our application setting object. Now we want to map
this to setting to an environment variable
called JWT secret key. But as a best practice, again, as I told you before, it's better to prefix this with our application name so you don't end up one application setting overriding another
application setting. So fair wheels align
JWT secret key. Now with this, we can go back
to our Auth module, so Js. So this is one place
we have referenced the secret we're going to replace this with a call
to Confic dot get method. So first on the top, we need
to import the config object. So Cs config. We set this to
require confit. Okay. Now, back in the post method, we call config dot cat and pass the name of the
application setting. In this case, JWT secret K.
So now, this is not a secret. It's a name of our
application setting. The actual value,
the actual secret will be in an
environment variable. One last change, we need
to go to index dot js. When the application starts, you want to make sure that this environment
variable is set. Otherwise, we have to
terminate the application because our
authentication endpoint cannot function properly. Once again, on the top, we load the config module. Config, we set this
to require config. Now, one more time,
we're going to call config dot get pass the name of the application setting
JWT secret key. Now, if this is not defined, we're going to log
a fatal error. JWT secret key is not defined. And then we need to
exit the process. Earlier, you learn about
this process object. This is one of the
global objects in node. Here we have a
method called exit. We give it a code, zero
indicates success. Anything but zero means failure. Quite often, we use exit one if you want to exit the
process in case of an error. So currently, I have not set
this environment variable. Let's run the application
and see what happens. So Norman index dot JS, Okay, look, we got
this fatal error. App crashed, waiting for
file changes before start. Now you might think
the application is still running because
Norman is still there. It's not terminated. But if you go to
Postman and send an SDDP request to
the application, you will see that our
application is not responding. So if the application crashes, Norman is still working. Norman is still running. Let me show you what I mean. So if instead of non mod
I run application with node see the
application crashed, and now we are back
in the terminal. So we are not responding to any request received
from the clients. Now, let's set the
environment variable. As I told you before, on Mac, you use Export, on Windows, you use set for command prompt and dollar
ENV column for Power Shell. Dollar ENV column, fair wheels, underline, JWT Secret
key. We set this too. Let's say my secure key. Now let's run the
application again. Node index or Js.
Okay, beautiful. We connect it to
Mongadib and if we go back to Postman and send
another request to Login, here is our valid cheese and web token that is signed
with our secret key, which is stored in an
environment variable.
12. Setting Response Headers: So in the current
implementation, when the user logs in, we generate a JCN web token and return it in the
body of the response. Now let's take this
application to the next level. Let's imagine when
the user registers, we want to assume
they are logged in, so they don't have to
log in separately. Of course, this requirement does not apply to every application. Sometimes you want to enforce the user to verify
their email address. So after they sign up, you send them an email, they
click on a link. So the process is different. But in this course, let's
imagine that Fair Wheels is an application that runs
locally in a car rental. So people who use this application are people
who work in this car rental. We don't need to verify
their email address. So the first day
they join the store, they need to create an account, and boom, they're logged in. So let's go to our
user's module. Okay, here's the post method. This is where we
register a new user. Now, if you look at the
response we are returning here, we're returning an object
with these three properties. Now we can add the JCN web token as another property here, but that's a little bit ugly because it's not a
property of a user. A better approach is to return the JCN web token
in an SDDP header. So just like we have
headers in our request, we also have headers in
our response object. So I'm going to go back to our Auth module and borrow this line of code
for generating the token. So copy this now back
to the user's module. Before we send the
response to the client, we generate the token and then we call a
response dot header. With this, we can set a header. Now, for any custom headers that we define in
our application, we should prefix
these headers with X. Now we give it an arbitrary name like authentication or token. This is the first argument, which is the name of the header. The second argument
is the value, which is in this
case, our token. So with this simple change, we set this header and then send this response
to the client. Okay? Now on line 20, we are using JWT as well
as the confit module. So we need to import
these on the top. So constant TWT we set this
to require JCN web tooken. And similarly, constant config, we set this to require confit. Now let's test this. So
in the last lecture, I ran the application with
node instead of nor mode. So my changes are not visible. So we need to stop the
application and run it with node mode. Okay, beautiful. Now, back in Postman here on this tab for
registering a user, I'm going to change this email to an email that I have
not registered before. Sent. Okay, take a look. So here's the body of the
response exactly like before. Now in the Headertab Look, we have this new
header, X token. And this is set to
our JCN webTken. So in our client app, when we register a user, we can read this header. We can store this JCN
WebTgen on the client. And next time we are going
to make an API call, we will send this to the server.
13. Encapsulating Logic in Mongoose Models: Now, there is a problem in
our current implementation. In the user's module on line 22, this is how we generate
a JCN web token. We have the exact same core
in Auth module on line 20. Now look at the payload
of this JCN web token. In this payload, currently, we only have the ID property. Chances are tomorrow
we are going to add another property in this pay
maybe the name of the user, maybe their email address,
maybe their role. We want to know if there
is an admin user or not. With the current implementation, every time we want to
change this payload, we have to remember
that we have to go to another module and make
the exact same change. And in the long run, you're going to forget about
these requirements. So in this lecture,
I'm going to show you how to encapsulate this
logic in a single place. Now, where should we
move this logic to? An amateur programmer
may think, Okay, I'm going to create a function like generate
authentication token, put this function somewhere
that we can reuse, maybe in another
module that we can import in both Earth
and user's modules. And with this, we have the
logic in a single place. Well, that is true. That works. But with this approach, you will end up with a lot of functions hanging
all over the place. In object oriented programming, we have a principle called
information Expert principle. That means an object that has enough information and is
an expert in a given area. That object should
be responsible for making decisions and
performing tasks. As a real world example,
think of a chef. A chef has a
knowledge of cooking. That's why the act of cooking in a restaurant is done by a
chef and not by a waiter. The waiter doesn't have
the right knowledge, the right information about
cooking at a restaurant. So if chef is an object, we should give the act
of cooking to the chef. Now, take this principle
and apply it in this code. So here, as part of creating
this Chase and web token, what do we need in the payload? We need the ID of the user. Tomorrow, we may need the name of the user
or their email. So all this information is encapsulated here
in the user object. So it's the user
object that should be responsible for generating
this authentication token. So the function
that I wrote here, generate authentication token, that function should not be hanging somewhere
in a module. That should be a method
in the user object. So here we have a user object that we load from the database. We need to add a method in
this user object like this. User that generate
authentication token. And this will give us a
token, simple as that. Now, how can we add this? We need to go to our user
module where we define the user model and make a
simple change there. All right. So here in our user model, we need to extract the definition
of this schema and put it in a separate constant because we are going to
work with that separate. So I'm going to
select all this coe. So when creating the user
model as a second argument, we pass user schema, and we have not
created this yet. We are going to do that now. So constant user schema, and we set this to this
expression like this. Okay. So here's our user schema. Now, you want to add a
method in this schema. So user schema that
we have a property, methods, this returns an object. We can add additional key
value pairs in this object. So we can add a key, generate
authentication or token. We set this to a function.
So key value pair. When we do this, then our
user object will have a method called generate
authentication token. Now here, in this function, we can have parameters. So if we have a parameter here, then when calling this method, we can pass arguments. In this case, we don't
need any parameters, so I just need to grab this logic for
generating the token, get it from here and move
it inside this new method. So here in the payload, we need the D of the
user. How do we get that? Well, this method will be
part of the user object. So in order to reference
the object itself, we replace user with this. And this means here you should use the regular
function syntax. You cannot replace this
with an arrow function. Because as I told you before, arrow functions don't
have their on this. This in an arrow function references
the calling function. So typically, we use arrow functions for
standalone functions. If you want to create a method
that is part of an object, you should not use
an arrow function. Now, let's revert this. So here we have the token, and finally, we return
it as simple as that. Here we are referencing
JWT and conflict modules. So we need to import
this on the top. So I'm going to go back to
our Auth module on the top. We no longer need these
required statements. So I'm going to get
these from here. And put them on top
of the user module. Now, back in the
authentication module, so we generate the token and
then send it to the client. We need to make the same
changes in our user's module. User's module, we
delete this line and set the token to user
that generate authToken. So finally, let's test this and make sure
everything is working. So I'm going to register
a new user here. Send, beautiful. And here's our
authentication token.
14. Authorization Middleware: So at the beginning
of this section, we decided to protect
the operations, that modify data and make them available only to
authenticated users. So let's go to our companies dot CHASE Piers the post method
for creating a new company. This API endpoint should only be called by an
authenticated user. So how can we enforce this? Well, here, we did
some logic like this. We need to read the
request headers. So this request object has
a method called header. Here, we specify the
name of the header, that is X token. We expect a JCN web token
stored in this header. So we store this
as constant token. Now, we want to validate this. If this is valid, then we give access to
this API endpoint. Otherwise, we will return
a response like this. Response for the status
code of four oh one, which means the
client doesn't have the authentication credentials
to access this resource. So this is the big picture. Now we don't want to
repeat this logic at the beginning of every route
handler that modifies data. So we need to put this logic
in a middleware function. Remember, middleware
functions, we talked about them in the section called
Express advanced topics. So we put this logic in
a middleware function, and then we can apply
that function in route handlers that need to
modify data. Let me show you. So let's get this from here. Alright, here we want to add a new folder called middleware. So we put all our
middleware functions here. In this folder, we had
a new file t dot has. Now here we want to
define a function called OT that takes three parameters, request response, and next, which we used to pass control to the next middleware function in the request
processing pipeline. If this concept sounds
unfamiliar to you, you need to go back
to the section called Express Advanced Topics because there we explore middleware
functions in detail. So here in this funtion, we should implement this
logic. We get the token. The chances are we don't
have a token at all. In this case, we return a 41 response with a
message like this. Access denied. No token provided. So this helps the
client to figure out why they cannot
access this resource. Now, otherwise,
there is a token. We need to verify that
this is a valid token. So here we need to use
our JCN Web token module. So on the top, let's require JCN web
token and store it in JW. Now here we call jwt dot Verifi. As the first argument,
we pass the token, and as the second argument, we pass the secret key
for decoding this token. So we stored that secret key
in an environment variable. We need to use a config
module to read them. So require config
and store it here. So just like before,
we call config dot g JWT Ccret key Now, this verify method will
verify our JCN webTken. If it's valid, it will decode
it and return the payload. So here we get the
decoded payload. However, if this
token is not valid, it will throw an exception. So we need to wrap this line
with a try cache block. So try Cache, we
get an exception. If we get here, we want to tell the client that this
is an invalid token. We set this status to
what 400 because there is a bad request because what the client sends us doesn't
have the right data. So send invalid token. Again, with this error message, we can troubleshoot the
authentication issues. So if on the client, we cannot access a given API endpoint, we'll look at the error message. We realize we send
an invalid token. Then we'll look at the logic on the client where we get the token and send
it to the server. So this is our cache block. Now, back to the tri block, here we have the payroll. So we can put that
in the request. So our request object, we add the user property to it, and we set that to this decode. So earlier, we put only the ID of the
user in the payload. Let me show you. So let's
go to our user module. Here's our user's schema. We added this generate
authentication token method. We created the JCN webTken
and this is our payload. So when we decode this JWT, this is the object we will get. And we will put this in the
request as a user object. So in our route handler, we can access request dot user dot underline
dot ID and SOW. Okay? So in the tri block, we set request dot user, and then we need
to pass control to the next middleware function in the request
processing pipeline. In this case, that's our route
handler. So we call next. So as I told you before,
in middleware functions, we either terminate the
request response sl cycle or pass control to the
next middleware function. Now, just a tiny
issue in this code, in case we don't have a token, we are going to send this
response to the client. But I want to make sure that
we exit from this function. All right, we are done with
our middleware function. Now at the end, we set
module dot exports to OTO a shortcut for that
is to set it right here. So we set module dot exports, we set it to a function. We don't need a em here. And then we can get
rid of line 17. Done.
15. Protecting Routes: So now that we have a
middleware function, we can go to index dot JS, Look, here we are applying
middleware functions. So we can add that here, and then it will be executed
before every route handler. But we don't want
to do this because not all API endpoints
should be protected. Some of our API endpoints
should be public, like registering
a user or logging in or getting the list of
companies or customers. So in this case,
we want to apply this middleware function selectively to
certain endpoints. So back to the company's module, here's our post route handler. The first argument is a route. The second is
optionally middleware, and the third will be the
actual route handler. So let's go on the top, impart this middleware
function. So require. Now we need to go one
level up and then go to the middleware folder and
load the Earth module. We get it and put it here. And finally, here
in the post method, we pass OT as a middleware function to be executed before this other
middleware function, which is in this case,
a route handler. So now let's test
this back in Postman. I'm going to open a new tab, send a post request to
the company's endpoint. So STDP local host,
API Companies. Now, in this case, I'm not worried about the
body of the request. I just want to know if we can call this API endpoint or not. So send Okay, look, we got this 401 or
unauthorized error. And here's the error message. Access denied, Noto can provide. Now let's provide
an invalid token. So we go to the headers tab, set the key that is X token. I put an invalid token, send, we got 400 or bad
request with this message. Invalid token.
Beautiful. Now finally, let's go back to our
register, use a tab. We got a valid
authentication token. So let's copy this
back to the third tab. Put it here and then send. Okay, now we got a bad request, but this is not because
of the authentication. This is because we expected the name property in
the body of request. So if I add a JSON
object here in the body, name new company. Now, we should be good. Send.
So we got a 200 response, and this is the new company. So as an exercise, I want you to apply this
middleware function to other route handlers
that modify data.
16. Getting the Current User: In a lot of applications, there are times
that we need to get information about the
currently logged in user. So in this lecture,
we're going to add a new API endpoint for
getting the current user. So let's go to our users module. Currently, we only have
one route handler. That's for creating a new user. Now we need to add another
handler for get methods. Now here, add a path or route. We can pass a parameter, but this means the client should send the
ID to the server. While this approach
is perfectly fine, there are times that perhaps
for security reasons, you don't want to
have an endpoint like this because then I
can send the ID of another user and look at their
account where there might be some information that perhaps should not
be visible to me. So the approach that we use quite often to get
information about the current user is to have
an API endpoint like me. With this, the client is not
going to send the user ID. We get it from the JCN Web took. So I cannot forge someone else's JCN WebTken because I told you that
in order to do so, I need to create a
new digital signature for that JCN WebTken. So let's add the route handler, Async request and response
goes to this code block. Now, this API endpoint should only be available to
authenticated users. So here we need to add
our OT middleware. On the top, let's load
OT from middleware Auth. Just to clarify, here, author
represents authorization, not authentication
because authentication is about validating
the username password. Here, we want to see if the user has permission to access
a resource or not, and that is authorization. We add our middleware here. Now with this middleware, if a client doesn't send
valid JCN web token, we will never get to
this route handler. However, if you get here, as you saw in the
implementation of this middleware function here, we will have request
dot user object. So we can access the
ID property here. So instead of passing the ID property in the
path or in the route, we get it from request
dot user dot ID, which actually comes
from our JSON web token. So this is more secure approach. Now, we want to find a
user by the given ID. So we call user dot fine by ID. Pre pass this value, then await the promise and
get the user object here. Now, we don't want to return the user's password
to the client. So here, let's call Select and exclude the
password property. In your application, maybe you want to exclude other
properties as well. Maybe address,
maybe phone number. So here we have a user object. Finally, we send
this to the client. So send user as simple as
that. Now, let's test this. So here in Postman, I'm going to add a new tab, send a Get request to Local
host for 3,000 API users me. Now, initially, I don't
want to send a JCN webTken. So send Xs denied no token,
provided. Beautiful. Now let's go to the headers tab. Add X or token here, and then pass a wedded
cheese and web token. SN. This is my user account. We can see password is excluded.
17. Logging Out Users: So in our Auth module, we define this route for
authenticating users. What about logging out users? Do we need a separate
route for that? No, because we are not storing this token
anywhere on the server. So we don't need a
separate route handler to delete this token. So technically, you
need to implement the logging out feature on the
client, not on the server. So on the client application, when the user wants to log out, you simply delete that
token from the client. Now, I've seen courses
and tutorials that teach you to store this token on the server in the database. This is a very bad practice
because these tokens are like keys that give a client access to protected API endpoints. If a hacker can get
access to your database, they can see all these tokens
for authenticated users. They don't even need to know
the password of a user. They can simply get
their authentication token and send it to the server to execute
request on behalf of a user. You should not store
tokens in your database. And if you know
what you are doing and you really want to store
the token in the database, make sure to encrypt it.
Make sure to hash it. Just like the passwords. Storing a token in
a plain text in database is like getting the passport or driver's
license of all users, put them in a central
place, and then anyone, any malicious users who has
access to that central place, they can simply get
these passports. They can get these
driver's license and imitate other
users, other clients. So once again, do not store
the tokens on the server, store them on the client. And as a security best practice, whenever you are
sending the token from the client to the server, make sure to use HTTPS. So a hacker sitting in the
middle sniffing traffic, they cannot read the token sent from the client
to the server, so the data is encrypted between the client
and the server.
18. Role-based Authorization: So far, we have implemented authentication and authorization successfully. Now let's take this
application to the next leve. Let's imagine certain
operations such as deleting data can only
be performed by Admins. So let me show you how to implement a role
based authorization. First, we need to go
to our user model. So this is our user schema. Currently, we have
three properties, name, email and password. Now, we need to add
another property to see if a given user
is an admin or not. So let's add a new property
is admin of type Boolean. So that's the first step. Now I'm going to go
in Mongo DB Compass, grab one of these users and
make them an admin. So edit. I'm going to add a
field after password, and the order doesn't
really matter. So is admin and by default, you can see the type
of this is string. We are going to change
that to Boolean. So let's say this to true and then finally apply the update. So here I have a user
that is an admin. Now, when the login,
I want to include this property in our
JSON Web Token payload. So next time they send this
JCN WebTken to the server, we can extract this property
directly from the token. We don't have to get the ID, go into the database and see
if they are admin or not. And again, as I told you before, with the digital signature
included in a JCN webtken, a malicious user cannot change the value of his admin
for their user account. If they make any changes, they have to regenerate
the digital signature, and this requires
knowing the private key that we stored in an environment
variable on the server. Now, back in our user module, when we generate the
authentication token, we want to add this
property in the payload. So here's our payload. Let's add a new property
is admin. We set this to. This dt is Admin. So this is the benefit of encapsulating this logic
inside the user object. So there is a single place
that we need to modify. Previously, we had this
in two different places. And with this symbol change, we had to remember to apply this change in two different
places in the code. So this is our knee token. Now on the server, we need a new middleware
function to check if the current user is
an admin or not. So here in the
middleware folder, I'm going to add a
new file, admin Js. So here we set module that exports to a
middleware function. And takes a request, a response, and a reference to the
next middleware function in the request
processing pipeline. So here we are assuming that
this middleware function will be executed after our authorization
middleware function. So our authorization
middleware function sets request not user, so we can access that
in this function. So I request that
user that is admin, or let's say if they
are not in admin, we're going to return
response status. We set the status 24 oh
three, which is forbidden. This is one of the areas that a lot of developers get wrong. So we have 41, which means unauthorized, and we have four oh three,
which means forbidden. We use unauthorized when the user tries to access
a protected resource, but they don't supply
a valid JSN webTken. So we give them a chance
to retry and send a valid JCN WebTken now if they send a
valid GSN web token and they're still not allowed to access the target resource, that's when we use
four oh three, which means forbidden, which
means don't try again, we just can't access
that resource. So back here, we set the status and send a
message like access denied. Now, otherwise, if
the user is admin, we pass control to the
next middleware function, which is, in this case,
the route handler. Now we are going to apply this middle ware function
on one of our routes. So back to companies dot JS, let's look at the delete method. So, here's our delete method. We want to apply two
middleware functions here. So we need to pass array. The first one is OT and
the second one is admin. So these middleware functions will be executed in sequence. First art if the client
sends a varied Jas in webtken and then we'll get to the second
milware function. The user is an admin, then the third
middleware function or route handler
will be executed. Now, we need to import
this on the TA. So constant admin,
we set this to require go to the
middleware folder and load the admin module. Now, let's test this.
Back in Postman. I'm going to open
a new tab and send a delete request
to this endpoint, STDP local host for
3,000 API companies. Now let me grab an ID for a company from the
Mongo IV database. So go to Companies
Collection, here, copy this ID and put it in
our endpoint like this. Do not forget to
include the odds token. Otherwise, you will get
the authorization error. Excess denied, no
token provide it. So we will put the token
and now send this. Beautiful. We have
a 200 response, and here's the company
that was deleted.