This is the second part of the Express Sessions Exercise.
We have completed the first part earlier,
where we added support for Express Sessions,
and use Express Sessions as a way of tracking the users on the server side,
and recognizing the incoming requests from the users.
In this exercise, we're not concentrating so much on Express Sessions themselves,
but we will look at a way of extending our express REST API server to support
a new model for registering and authenticating users.
So, we will introduce a new user model and schema into our application.
We already have the slash users route which
the express generator has already mounted a user's router.
So, we're going to leverage that and then update that
to support registration of new users,
authenticating an existing user,
and also logging out a user from our server site.
So, we'll look at Express Sessions as a way of tracking the users once the user logs in,
and then when the user logs out,
the session will be removed from the system.
So, how do we go about doing this extension in this exercise?
Let's go and find out.
Continuing with our express REST API server,
let's now go into the models and add a new file named user.js.
This is the file where we will create the user schema and the model.
We'll create a simple user schema which tracks the username and password,
and also a flag that is set to indicate
whether the user is an administrator or a normal user.
So, this is one way of distinguishing among different user types.
So, in this file,
let's first require Mongoose because we are creating a Mongoose schema,
and then we'll create a schema named Mongoose schema,
and then we'll say var user.
So this is the user schema that we're going to create,
and we'll say new user schema.
The user schema will be defined as I just mentioned,
with three fields in here called username
which obviously would be of the type string,
and this is a required field,
and this is a unique field.
You don't want two users with the same username in your system,
so we create the username and then the second field is password field.
Now, of course, you can extend
this user schema to allow the user to track their entire profile,
but we will do that in one of the later exercises, for the moment,
a user is recognized within the system by simply the username and password.
So, the password is of the type string and then is required.
The third field that I'm going to use is called the admin.
So the admin is of the type Boolean,
and we'll say default false.
So, by default when a user is created,
a new user is created,
the admin flag will be set to false.
You can explicitly set it to true from within
your code in order to mark a user as an administrative user.
Perhaps you might provide additional privileges to an administrative user,
or perhaps allowing administrator user to perform
certain operations that normal users would not do so.
We will look at that in one of the later exercises,
for the moment, this is the user schema that we have created,
let's create a module out of this and export from
this module a Mongoose model of the name user.
So, this is the Mongoose model and the schema is
the user schema that we have just defined a little bit earlier.
So, this is how we define our user schema.
So, now that we have defined our user schema,
let's go into the router that has already been set up
by express generator when it generate this express application.
So, if you go into the routes folder,
you will see this file called users.js.
So, this users.js file will be extended to create the router.
So, right there what I am going to do is to import body-parser,
and then they'll declare the express router,
and then we'll say also import
the user schema and
model from models user,
and then for the express router we'll say,
router use body parser.
So, now that we have declared the body parser in there,
we're going to leave this part as such,
later on we will modify this to allow the administrator
to be able to retrieve
all the users that are registered in the system but for the moment,
we're going to leave that route as such.
We will add in a few more routes here,
so we'll say, "Router post."
So we'll support the post operation on a route called signup and as you expect,
this signup route will allow a user to signup on the system,
so this will support the sign up of the user.
So, we'll say, "Rec, res, next."
And so this would be the router post signup,
the remaining methods will not be allowed on the signup end part.
So, to access this,
since this users router is mounted on slash users,
we would specify this endpoint as slash users slash signup,
and this is the end point that will be used to sign up new users within the system.
So, first thing that we will do is use
the user method and the expectation is that for a user to sign up,
the username and password will be provided as a JSON string
inside the body of the incoming post request.
So, from the body,
since the body would have been already parsed by the body parser,
so from the body,
will first check to make sure that
the user with that username doesn't exist within the system.
If the user with that username exist,
then you are trying to sign up a duplicate user
and that should not be allowed in the system.
So, we'll say, "Users find one," and then we'll try to find if
there is one user with the username that has been
selected by the client that is trying to sign up a new user.
If the user already exists,
then obviously you won't allow the new user to sign up with the same username.
So, we'll say, "Then user."
So, this will return the user here and inside this user field,
then we will check for the fact whether the user already exists,
and then let me catch the error here.
So we'll say, "Catch error," and then, "next error."
So, we'll just pass that on to the error handler there.
So, so if this search for the user returns the user field,
if user not equal to null.
So, if the user that is returned by this search is
not null then that means that the user with that given username already exists,
so you should not allow a duplicate signup.
So we'll say, "Var err new error."
And we'll say, "User req. body username."
Already exists. So basically,
you are preventing a duplicate user from signing up and then we'll say
err.status 403 versus the forbidden and then exit,
calling the error handler, next err.
Else, this means that the user doesn't exist
so you should allow the user to be signed off.
So, in the else part,
we will say, return user.Create().
We'll create a new user with the username set to req.body.username,
and then let me put this in the next line so that it's more clear to you,
and we'll say password: req.body.password.
Now, we already know that the admin flag by default will be set to false,
so we're going to leave it as such,
and this will let our new user be signed up and when the new user is signed up,
this will return a promise and inside the "then" we will handle this promise here.
So, this will return the promise from
this "then" and then we'll handle it in the next "then" here.
Will say then, res.statusCode is 200,
res.setHeader and we'll say content-Type application/json
and we'll say, res.json(status: Registration Successful),
and if you want,
we can load the user into
this reply message here as a property in the json.
Will say Registration Successful.
Then if there is an error in
this operation will say "next err".
So that'll handle the error.
If the promise doesn't resolve successfully,
then will be handled by that. So that's it.
So here, we have a way for the user to sign up.
So, for the user to sign up,
the user will do a post on /users/signup and in the body of the message,
the client will include a json string
with username and password properties in that json string.
That's how you sign up for a new user.
Now, let's see how we will login the user.
Now, we will still use the Express sessions that we have done earlier to track the user.
So, to logging a user will say "router.post" on the end point/login.
So on the /login endpoint,
we will do a router.post.
We will obviously instead of saying function,
you can use the arrow function here for the router.post,
I'm going to do the same thing here.
I've gotten fond of arrow functions.
So, we'll do an arrow function here.
So for the login, how does the login proceed?
So for the login,
what we will do is that we will go to the app.js file and then inside this auth,
we were doing that sign up for the user there.
So, what I'm going to do is I'm going to copy
this whole thing because I wouldn't be doing all this anyway.
So, rather let me copy all the way from this point onwards up to the req.session.user.
So, the if part of req.session.user I'm going to copy,
and then coming to the users.js and for the login.
It is exactly how I'm going to do the authentication.
So we'll say if not req.session.user.
So that means that the user has not yet authenticated himself.
Then, you expect the basic authentication as
a mechanism for the user to use for authenticating.
So we'll say var authHeader if!authHeader then we will raise the error,
otherwise we will retrieve the username and password from the header.
Now, here we were doing if username is equal to admin and password is equal to password.
But now, what we're going to do is we're going to search in
the database to see if that particular user exists.
So, instead of doing this here,
instead of doing this if username and password,
we will say, user.findOne
and we'll say username is username.
So this property is equal to this username that we have just retrieved
and then we'll say then user.
So inside this "then".
So I'm going to move this code inside this "then" because
now what I'm going to check for is now that I have retrieved the user,
I need to check to make sure that this user is exactly what I am looking.
So at this point,
we're going to first check to ensure that the user is not null.
So we will say if user is null.
So, if the user is null,
so which means that we couldn't find a user with that particular username.
So, you will have to return an error here.
So let me just copy this part and then paste it in
here and then we'll return saying var new Error
and we'll say
user space username
does not exist.
So in this case,
this user doesn't exist so I'm just going to remove this part and
then the corresponding error status would be 403 here.
So this is the first part.
If the user is null then we are obviously going to tell that the user does not exist.
The second part we're going to check is Else if user password.
So which means that the user exists at this point.
So, the second check that we will do is that the user password is not
equal to password then.
We again need to indicate the error there.
So the error would be saying,
in this case, we'll say
"your password is incorrect".
So that's the second part here.
So your password is incorrect and then the last part.
We will say "else".
So let me indent this code.
So else if user.username
is username which obviously should be true in this case.
Then the second part,
user.password is equal to password which also obviously should be correct at this point.
But in any case,
I'm going to check for that issue here and this else will not occur at all in this case.
So, by the time you reach this point,
the username should be the
same as the username and password should be the same as a password,
but in any case I put in a double-check at that point just to be doubly sure.
Then in this case,
then we'll say req.session.user is authenticated.
So, we will set this to authenticated
and also this one, we will say
res.StatusCode is 200.
So we were successfully able to authenticate the user.
So, we'll say res.StatusCode is 200 and then we'll say res.setHeader
Content-Type text plain and
res.end we'll just simply send a message saying "You are authenticated".
That's it. So, this covers the then part of the user findOne.
So first, we check that if the user is null,
that means that we couldn't find the user,
so we're obviously returning an error saying that the username does not exist.
If the user's password does not match the password,
so at this point,
the user exists but the password didn't match, so we'll say,
"Your password is incorrect", and then, finally,
we reach this point then the username and password should be correctly identified.
Although I don't need this check but I just put it there in place and then at this point,
I will set the req.session.user to authenticate
it and then set the status code to 200 which means that you were
successfully able to authenticate the user and then you can finish at that point.
Because this is a then,
I will put in a catch at this point,
so we'll say catch error and if the error occurs,
then I will simply pass the error over
to the next so
that the error handler will be able to deal with the error appropriately.
So that finishes off this user findOne.
Now, this is the case when the req.session.user is not set.
If that is already set,
then that means that the user is already logged in.
So in this case,
the else part here deals with the situation saying we'll set
the statusCode to 200
and the Content-Type to text/plain and then we'll say,
"You are already authenticated."
So, if you reach this else part,
you would come here because the req.session.user is already not null,
so which means that the user has already been authenticated,
so which means when you reach this point,
then the user has already logged in earlier,
so you don't need to verify.
So you will simply say,
"You're already authenticated," and then finish off at this point.
Okay. So now, the last method that we will implement is for logging out the user.
So, we'll do a router.get on /logout.
You must be wondering why do we do a get on
the logout rather than a post which we did on login?
On login, you need to submit the username and password.
For logout you're simply logging out yourself from the system,
so you don't need to supply any further information because
the server already is tracking you based upon
your session ID and inside that session cookie here.
So, that's why we are not
explicitly needing to send any further information in the body of the message.
So we'll say if req.session so which means that the session must exist,
otherwise, you're trying to log out a user that has not logged in.
So it doesn't make sense.
Now, the session itself provides this method
called destroy and when you call the destroy method,
the session is destroyed and the information is removed
from the server side pertaining to this session.
So, which means that if the client tries to again send
the session information which is stored in the form
of a signed cookie on the client side,
that will be invalid.
So we need a method of deleting the cookie that is stored on the client side.
Now, this operation will remove
the session information from the server side so that the session is no longer valid.
So, at this point,
we'll say req.session.destroy and then we'll say, res.clearCookie.
So the clearCookie is a way of asking the client to
remove the cookie and the cookie name is the session ID.
So, in the previous exercise,
we saw that the cookie was stored with the name of session ID on the client side.
So we are asking the client to delete this cookie from
the client side in the reply message and then we'll say,
res.redirect and we'll redirect it to the homepage here.
So, this is a way of redirecting the user to enter their standard page,
so for example, the homepage of your application.
So, this is the way you would handle the logout of the system.
If the req.session doesn't exist,
then that means that you're not logged in,
so we will have to generate an error.
So we'll say var err,
new Error, "You are not logged in",
and we'll set the error status to 403,
this is a forbidden operation and
then generate the error to the error handler, that's it.
So now, you see that we have extended the user's router to support three new endpoints,
the sign-up endpoint which allows a user to sign up,
the login endpoint which allows a signed up user to login,
and then the logout endpoint which allows a logged in user to log out of the system.
In the process of logging out,
you're destroying the session on the server side,
you're also clearing the cookie on the client side,
so that the client cannot be used in expired session to try to contact that server.
One minor correction in users.js,
this should be var User require../models/user,
recall that the users.js file is in the routes folder and then
the user.js file is in
the models folder which is up one level and then into the models folder.
So, this should be../models/user,
so make that minor correction, that's it.
We have modified the users.js file,
now the last thing that we need to do is go and fix up the app.js file.
In the app app.js file,
if you browse into the app.js file,
you would see that we have
this slash index here and slash users here after the authentication.
Now, this will not work for us because if you need to sign up,
then the user would sign up and log in before the authorization is confirmed,
anyway if the sign up and log in is for the process of signing up,
so I'm going to move these to up before the authentication step here.
So, we're going to move this up into this location.
So thereby, an incoming user can access the index file
at the slash and also access the users endpoint without being authenticated,
but any other endpoint,
the user has to be authenticated,
so that is the way we set this up.
Inside the function authentication, we'll say,
"If not req.session.user," then notice that this place,
we would not be allowing the user to come in here without the user having logged in.
So, if not req.session.user,
then we would simply say you are not authenticated.
This authentication has to be done by using the login method here.
So, I'm going to remove this whole part here.
So, we'll say, if not req.session.user,
we'll say you are not authenticated and so we will have to send a photo three here,
so which means that you should never come down into this point at
all without the user logging in.
So, we'll say you are not authenticated.
Recall that you need to authenticate yourself by doing a POST on
the slash users slash login end point.
So we'll say you are not authenticated, otherwise,
if req.session.user is equal to,
recall that in the login function in the
users.js we have set the next session user to the string authenticated.
So, that is what we are going to be checking for.
We'll say, if req.session.user is authenticated,
we'll say next, else you are not authenticated again.
Then we will send back a forbidden message. That is it.
So, with this update,
my app.js file is also ready.
Now, having done these updates,
let's go and check how our application works now.
Going to the terminal,
if your server is running stop it and then restart your server.
My server is not running at the moment,
so i will say npm start,
and my server should be up and running.
Let's now see how we will access the server.
Going to Postman, let me do a GET on
localhost:3000/dishes and then you will immediately see
that it complains saying you are not authenticated.
Obviously, I'm not authenticated here.
Let me try to access just the localhost:3000 and you will see that,
that root is available for us.
Now, of course, we want to first register a user and then log in
as the user and then thereafter get access to the rest of the REST API endpoints.
So, first, let me register a user.
To register the user,
I need to do a POST to the localhost:3000/users/signup.
For this POST message,
in the body of the message,
I need to include in the body of
the message a JSON string with the username.
So, let me login myself and
then I'll just use the password as password.
This is a valid registration.
So, when I sign up as a user with
this information in the form of a JSON string included in the body of the message,
let me see that the header now contains content type application,
JSON because I've set up the JSON in the body of
the message and then send the POST to the signup.
Then when I send the POST to the signup,
you immediately see what is returned by the server here.
So, you see that the server has returned saying,
"Registration Successful" and itself,
it gives me the details of the user here.
So, this is a record that exists in my MongoDB and
note that the model type is the user model type that we had setup.
So, we have the username and password and
the admin flag as you can see is set to false by default.
Once we register the user,
this user is now a valid user.
So, let me try to again POST the same user and see what happens.
So, when I POST the same user obviously,
there from the server-side,
it replies saying this user already exists.
So, you can see that we have set up our signup procedure to not allow
the user to duplicate register with the same user ID.
Okay. So, that's the second part.
Now, let's do a login of the user.
To do a login of the user,
let me remove the content type and then I will also
remove the body because for the login, that is not required.
So, I don't fill in anything in
the header and then try to login and when I tried to login,
it replies back saying you are not authenticated.
You are unauthorized.
Now, I need to log myself in.
So, this is where I can use the basic authentication.
So, in the basic authentication,
I'm just going to type in my username and
password that I just registered in the previous step and then update my request,
and then do a POST on the login.
So, when I do a POST on the login,
you see that the server replies saying, "You are authenticated".
So, at this point,
I can perform the GET,
PUT, POST and DELETE requests on all the other endpoints.
So, at this point, if I do a GET on the dishes endpoint,
you would see that this returns
the empty field because my database is currently empty at the moment.
Let me clear out the authorization and also from the header remove
the authorization and then do the GET and it's tool box just fine.
So, you can see that I am now able to
access the endpoints because I have logged myself in.
So now, let me do a logout of the user.
So, we'll say, localhost:3000 and logout.
Note also that, in the cookies,
a cookie has been set up at the session ID right there.
So, that cookie exists on my client site.
So now, let me log myself out by saying GET localhost:3000/users/logout.
So, when I do a log out of the user,
you will immediately see that I am redirected to
that localhost:3000 endpoint and also notice that the cookie is gone.
So, when I look at cookies,
you see that the localhost,
the cookie is gone because from my server side when I do the logout
it sends back a rest clear cookie to the client side and so the cookie is gone.
Now, if I try to do a GET
on the localhost:3000/dishes endpoint,
this will not work because I am not authenticated.
So, this demonstrates to you how you can extend your Express server to
allow users to register themselves and then allow the user to login to the system,
and then also logout from the system.
On the server side you are tracking a logged in user using the session and using
the cookie on the client side and when the user logs
out the cookie is destroyed on the client side.
With this, we complete this exercise.
This is a good time for you to do a Git commit,
with the message, Express sessions part two.