So let's implement authentication now.
And that means just to recall what you learned
in the authentication section,
that we wanna use the session that we have anyways,
and which unauthenticated users will have as well
to store extra data in that session,
which identifies a given user as authenticated.
Now, for this, of course,
we start our journey in the login form.
So in views, customer auth login.
Because it's the submission off the login form
and the validation of the submitted data
that will ultimately log a user in.
And for this, for example,
the action of that login form
should of course not point to /signup,
but instead to /login.
I'll still use a post request
because I will change something on the server,
I will change my session to mark that user as logged in.
Now, the rest of the form should be fine,
and therefore, we can now move to the auth controller.
There, we added a function for assigning users up,
we have a function for showing the login page here,
but we have no function for handling user login,
that's the next function we have to add here,
a login function.
As always, we get the request and response,
and we can now add our login logic in here.
Now, what is the login logic?
Well, in the end, we got a couple of steps to go through.
We have to check if we find a user
for the provided email address,
if we don't, we can't log the user in.
If we did find a user,
we'll have to validate the password
and see if the entered password matches the password
stored in the database,
if it doesn't, we don't have an authenticated user.
If it does match,
we do have a user with the correct credentials,
and then we wanna edit to session of that user
such that we basically reflect that fact,
that we stored that this user should be treated
as logged in.
So therefore, I'll actually start working in my user model
because there, I wanna add methods
for comparing the password
and for all the checking if that user exists already
if we do find a user for the given email address.
Therefore in there, I'll add a new method
which I'll call getUserWithSameEmail.
Of course, the name is up to you,
but this describes pretty well what this method will do.
And in this method here,
I want to reach out to the database,
of course, again, to the users collection,
and then in there, I want to call findOne,
which is a method provided by the MongoDB package
for finding exactly one user.
We pass an object to findOne
to set up the filtering criteria for this findOne query.
And here, the logic is that we can make a simple
equality comparison by first referring to the key
in the database documents we are looking for,
and then after the colon, the value we're looking for,
in this case, this.email.
If I find a user in the database
which has an email that matches the email
of this concrete object,
then, well, we have a match.
So therefore here, we could then await this
by adding async in front of get user email.
But actually I don't have any other steps I wanna perform
in this getUserWithSameEmail function,
hence I'll just return this promise,
which is yielded by findOne.
So getUserWithSameEmail will still return a promise,
even though we haven't added async
because I'm officially returning this promise,
because findOne yields a promise and I'm returning it.
So now back in auth controller in login,
I can get an existing user by waiting,
and hence we should add async here in front of the function,
by awaiting the result of calling getUserWithSameEmail.
For this, we first of all,
need to create a user with new user using our user model.
And here, I wanna create a simplified user
without all the user data like the name or the address,
instead with just the email and password.
And since email and password come first
here in the constructor,
we can call the constructor with just these two values
and omit the rest which therefor will be set to undefined,
which in case of logging in is fine.
So here I then get my request.body.email,
and my password with request.body.password.
And then on this user,
we can call getUserWithSameEmail,
and that returns a promise which I then await here.
And this then gives us an existing user or not,
we don't get an existing user if we don't have a user
for the given email address.
Hence I check if not existingUser,
if it's false here, so if it's undefined, for example,
in which case I know that we didn't find a user
for the given email address,
and therefor I wanna return,
and hence avoid executing any other code in there
which might come thereafter
because I don't wanna continue with authenticating
that user.
The visitor clearly entered an incorrect email address,
so I don't wanna continue.
Instead here, I can then redirect back to /login
to allow the user to try it again.
We'll refine this later so that the user input isn't lost,
for the moment, we just redirect like this.
If we make it past this if check,
we know that we have a user with the given email address,
and therefor as a next step,
we can check the password.
For this backend user model,
I'll add another method anywhere in the class,
exact position doesn't matter,
where I wanna compare the password
and hence I'll name it.
comparePassword or hasMatchingPassword.
The name is up to you, I'll go for this name.
Now in this method,
we use to bcrypt package,
the package we used for hashing the password before
to call the compare function offered by this package.
This compare function wants the unhashed password
entered by the user,
and then the hashed password to compare the two.
Now the unhatched password
is what we're currently storing in this password.
Because when I'm creating a new user here,
I'm storing the raw password in that user object,
not in the database, just temporarily in that object.
So therefore, compare first gets this.password,
the unhashed password entered by the user,
and then we need the hashed password,
so I expect to get this as a parameter value here,
so that we can pass this as a second argument
to compare function here.
Now compare returns a promise
and I will return to his overall code line,
And hence hasMatchingPassword in the end
will return a promise.
The promise returned by compare
is returned by this function, by this method.
So back in auth controller,
we can now also create a new constant,
passwordIsCorrect could be the name,
where we await the result of calling
userHasMatchingPassword.
And to hasMatchingPassword,
I pass existingUser.password.
So the password that we retrieved from the database
for this existing user that we got from the database.
Now, if it's not correct, if password is not correct,
hence the exclamation mark,
then of course, again,
I wanna return so that no other code in this function
is executed, and I wanna redirect to login.
If you make it past this if check though,
we know the password is correct,
and we know the user email is correct,
otherwise we wouldn't have gotten here.
And therefore now here, we want to log the user in.
We want to manipulate the session
such that we store some data in it,
that this user to which the session belongs
should be treated as logged in.
For this, I will actually add a new folder in this project,
a util folder in which I'll add an authentication JS file.
It's not a must to do,
but again, I'm splitting my code across multiple files
to keep every file on its own
relatively small and lean and manageable.
In this authentication JS file in the util folder,
I'll simply add a function, createUserSession,
and this function here will get the request object,
because we'll need that to access the session,
then the user that we created with all the data
that belongs to that user,
and finally, action that should be executed
once the session was updated,
so that if we redirect to a protected page, for example,
we only redirect one the updated session data
was saved back to our session store.
Otherwise, we might redirect too early
and reach the protected page before the user data
and the information that this user should be treated
as logged in was even stored in the database.
It's a problem we also discussed earlier in the course.
So then in createUserSession, I'll access rec.session.
This is a property that's made available
by the express session package.
Dot, and then any key value pairs of your choice.
You can store any data you want in the session.
For example, all store the uid, the user ID
by accessing user._id.toString.
_id is the ID format used by MongoDB.
Internally in the database,
every document gets this _id field
as a unique identifier.
And since this user which I expect here
is the user coming from the database,
it will have to _id field,
and I'm converting this to a string
because by default, the MongoDB ID
is this special object ID thing
which you saw before in the course,
and which you'll see again later in this section here.
And I wanna force a string conversion here
so that I'm simply storing a straightforward string
for the given user ID in my session.
And for the moment, that's all I wanna do here.
Now I will add module exports and export an object,
because I will soon add a second function to this file,
which should also be made available outside of it.
And I'll export the createUserSession function
by pointing at it.
Now back in auth controller JS,
we can of course import the auth util
by requiring going up on level util authentication.
And at the bottom of the login function,
we can now call auth util createUserSession,
forward our request object, which we have here
because we're in a controller action,
which also gets this request object,
now I'm just passing it along to createUserSession.
Then the existing user I retrieved from the database,
and then last but not least,
an anonymous function like this,
which should be executed once the session was saved.
And that's some logic we again have to add
to this createUserSession function here.
In there, after updating my session,
I want to call a request session.safe like this,
and pass action to that save method.
The save method is coming from the express session package,
and that package will execute save when we call this here,
and then it will execute action
once saving the updated session data to the database
is done.
So action will only be executed
once the session was successfully saved in the store.
And the action here is just anonymous function,
I'm passing to createUserSession here in the controller.
And inside of this anonymous function,
I will simply redirect to slash, let's say
to the starting page,
but I only wanna do that once the session data was saved.
And with this pattern, we ensure that this is the case.
Okay, so now we're creating that session data
once a user logged in successfully.
To see whether that worked or not.
I actually wanna add a page for a slash nothing,
so a starting page, you could say.
For that in the views customer folder,
in the products folder, I'll add all-products.ejs file,
because my starting page actually
should be the page showing all the products.
And on there, I'll copy the signup.ejs file content.
I will not output a form, so I will delete that.
But for the moment, I wanna say all products,
I will not include the auth or form.css imports,
and the page title here will simply be set to all products.
But then here, I'll add a paragraph where I say,
a lists of products,
and will output a real list of products here later.
For the moment, I'm just adding this
so that we can use this template as a starting page.
And of course, now we also have to add some routes
and controller actions that load this page at some point.
If we dart back in the routes folder,
I'll add a base.routes.js file
for some base routes like the starting page route,
and already a products.routes.js file
for the product specific routes.
And my idea now is to go to the auth.routes.js file,
copy that configuration over to product routes,
get rid of all the auth related logic,
also the auth controller import,
but simply to find a new get route for /products,
where I want to render this all products view
we just added.
Now we're soon going to add a dedicated controller.
For the moment, to quickly have some logic here,
I'll add an anonymous function here
where I simply render customer.
So this customer folder /products /all-products,
so that this all products template is rendered here
for a request to /products.
And then I'll copy that configuration here,
and move that into base routes,
because there, I wanna handle a request to just /nothing.
And here, I don't wanna render this template,
but instead, redirect to /products.
So that whenever you try to visit the starting page,
we actually redirect you to /products,
and for /products, we rendered as all products page.
So this routing configuration is added here,
now we just need to activate it in app.js.
And hence in there where I also import the auth routes,
we can now also include the product routes
by requiring ./routes/products.routes.
Let's maybe also name this constant products routes then,
and the base routes, which we get by requiring
./routes, base.routes.
And with those imports added here,
we can then use those routes as well
and use our base routes here.
Actually, maybe even before we reached the auth routes,
and then maybe after the auth routes,
though, at the moment,
the order doesn't matter too much,
I'll add my products routes, like this.
Now, redirecting to slash should work after we logged in,
and therefore we are now ready to,
well, check and test our login logic.
However, at the moment,
we won't really be able to tell whether we succeeded or not,
because at the moment, nothing on our page will change
if a user does login successfully.
We redirect, yes,
but other and that,
we don't get any visual feedback anywhere.
And therefore let's actually first tackle that
in the next lecture,
before we then test this login flow.