- [Instructor] We are now going to implement
so-called Passport Strategy,
that tries to authenticate the user against the database.
To kick this off,
you first have to of course installed Passport
and also the Local Strategy Module.
For that, I'm stopping the Node.js process
by pressing Control C in the console
and there now type npm install -S passport passport-local.
So that's the LocalStrategy.
The LocalStrategy is always used when you have to implement
your own logic on how credentials should be verified.
In our case, now this means that
we will validate against MongoDB.
I already prepared the empty module
where we will implement all the authentication logic.
So I'm going to open Server, Lib,
and in there, there is Passport in the Index.js file.
So you see, this is an empty module,
we just load the user service there already
because we will use it to talk to the MongoDB.
I will also start the Node.js process
again by running npm run dev.
And now first I will bring in the passport module by adding
const passport equals require passport.
And then I will load the LocalStrategy by adding const
LocalStrategy equals require passport local.Strategy
after the parenthesis.
And now in the module body starting with line 11,
we can now start implementing this strategy.
And for that we use passport.use
so that's a method provided by passport and it look similar
to what we have in express.
And in there, I will now add new LocalStrategy
and LocalStrategy as constructor and into this constructor,
I will now first add a config object and there,
I first add passReqToCallback and that's true.
And this is needed so that we get the request object back
into the callback that we will define later
and we will then use to request object again,
to access the session object, to create flash messages.
There are more settings that you could add here.
For instance, you could add usernameField.
Because passport will try to extract username and password
from the payload as we did it before
with request.body and it assumes that the
usernameField is username.
And then there is also the passwordField that we could set,
and the default is password.
So as you see this resembles what we have anyways,
so a usernameField is called username
and our passwordField is called password.
So we'll remove that again.
But for instance, if you want to use the email address
as usernameField, so authenticating using email address,
then you would change it here.
Now, after this config object,
we will add the callback and the callback looks like that
so add a comma and then async internet define an anonymous
function, and this now gets request,
because we have this passRequestToCallback true
the username, the password, and then also
a done callback that is then used to return
from this function.
Alright, after pressing Save,
this is now looking a little bit more pretty.
And now in there,
I will start with adding a try-catch block
pass between asynchronous things in there,
and we have to catch that.
And in case of an error, we will return done error.
And now we can define the logic that is needed to verify a
user against the database.
And this is actually very similar to what we did before in
the login route so I will now head to Routes,
auth, login.js and scroll down to the post login route
where we verify the user and this now here starts with
line 24, this I copy now,
and I copy down to line 49.
So this whole block now goes
also into this passport strategy.
And there we have to fix a few things
we don't use this error.push anymore
because error verification now works differently.
And this is how we use this request object
to provide the error to the user
and we have to here now use the done callback every time
something goes wrong so add return done, null and false.
So there is no error,
but also no user and passport will then know that this
authentication attempt failed.
And then we can remove the else block and make it a simple
if, and there again, in case the user is not verified,
we print out the error, but again, return null and false.
Then we can, again here now,
remove the else block and continue with the validation.
So now here line 34, we do the validation of the password
and if the password is not valid I again,
return done, null and false.
So we can now return done, null, no error,
but the user object and passport will know, okay,
now we have a user object so the authentication worked.
I have to add two more functions to set up passport
properly, this is serializing and deserializing users,
and you will understand what this is about in a second.
So after this declaration of the LocalStrategy,
I now add passport.serializeUser
and again, we define here a callback async.
And what do we return from our authentication function?
We return a whole user.
So first we get the user object
and this also has a done callback.
So the question is now, what will we store
will serialize into the session?
And as before we won't serialize the whole user into the
session, but just the user.id.
And this means that we will just return,
null, no era.user.id, this means that when passport
writes the user to the session,
it will just use the user.id for that.
As we have a serialized user,
we also have to find a way to deserialize the user
and this is again, similar to what we did before.
We have to fetch the user with
the given user.id from the database.
So add passport.deserializeUser
and this is an async function,
it gets id that's the user.id
and again, there is a done callback.
And as I'm doing that,
I see that actually the serializedUser
does not have to be async so let's remove
this async up here in line 49,
and let's continue by adding now, try catch again.
If there is an error, we will return done (errr)
and we will now load the user with the given user.id
from the database so I add const user
equals await UserService.findById
and there I know passing the id.
And then again, I will just return done null, user
and in this case now if the user was not found,
the variable will be empty and then passport
will also know that something went wrong.
So we don't have to handle this case explicitly.
And then in the very end here,
let's not forget this is important return passport.
So with that, we have a central module
where we configure passport
and set everything up that we need.
Next, we have to hook this module now up
with the rest of the application.
For that, I open app.js
and right after via requiring the UserService
I now add const setupPassport equals require,
and this is now in lib/passport.
And then you know that the passport is actually a function,
so we have to call this function somewhere.
For that I scroll down and right after
I'm instantiating express,
I can add const passport equals setupPassport
and we pass in the config object.
The config object is not needed right now,
but we will need it later.
And there are two more things we have to do to setupPassport
for that I have to scroll down
right after the session management is done.
And right after the cookieParser,
I now add app.use and passport comes with middlewares
that we have to call now.
The first is passport.initialize
so this initializes the actual passport library,
and then also as passport relies on information stored in
the session, we have to add app.use passport.session.
So this is where then actually passport for instance,
calls this deserialized to load the user
then from the session.
This and that's important has to run after the session
management was initiated because otherwise there is no
session available in express
and passport would not work properly.
And then we have to do one more thing in this file,
let's scroll down and you see that we're still here
loading the user by id
so there is a manual authentication management still,
and we can remove that now.
So we can remove the whole section here
from the if statement in line 75,
to down where we then deal with,
rememberme this will stay,
but we still want to have the user in our templates
and luckily this is stored in req.user
so let's remove request.user equals user
this is already set by passport,
but then we write request.user into res.locals.user.
So with that, we are back to how it worked before,
but now using passport.
Now, of course I see that (indistinct) is still marking
this file as red something is still, maybe wrong here
it looks red, yeah, we don't need the UserService anymore
because now we are doing that through passport
so let's remove that.
And now the last thing that is left is that I have to head
to login.js, there I can now remove this whole block
that I copied before so everything from line 24
to 49 can be gone so it's always a good feeling, right?
When we can remove code.
And we can also remove the whole error checking here,
because this is also not needed anymore.
We can remove the storing of the user.id into the session
this is all done the passport.
(indistnict) rememberme stays as it is.
And now we have to of course use passport.
And for that, I can now just simply bring in passport,
right on top of the file I add now const passport equals
require passport and passport as a module maintains a global
state so this passport object that you have here
now also have to have this LocalStrategy configured
that we set up before so this is the same passport object.
You can compare it with the Singleton.
And with that, I can now simply add
to the login route, to the post route,
the middleware of passport that is called authenticate
and for that I will now add passport.authenticate
and as first argument I have to now tell which strategy to
use, and this is in this case local.
And then it also takes a conflict object
and the error can define
where to redirect in case of a failure.
And now case the failure redirect is /auth/login.
So if this fails, we will be redirected, now let's save
and then it gets nicely formatted again.
And we still don't see an error when it reloads
so this is a good sign.
So now we have implemented the login now is using
passport, but we are missing one more thing.
And this is the log out because
now also the log out works differently.
So I will now use passport to log out and passport gives us
a handy little function on the request object,
which is logout so I can now call request logout
don't forget the parenthesis,
and now passport is handling the logout as well.
So now let's try this out,
let's head into the browser and first let's do the logout,
and this works and now let's login the user again,
let's log I think the user, yes
was lcola and I changed the password before
now it's my secret too type this in my secret too,
and the login and you see that this works.
So now we can log in using passport
and you may have figured that the code that we wrote was not
way less than we did before,
but now we have a central location where this is all handled
and its handled by passport,
but we will see more use cases for passport later
where it makes even more sense to use it.
But I want to show you one more important thing.
So we know that there is this My Account link,
and we only show this when the user is logged in.
But that's not really security, right?
So I could still access this route
even when I'm not logged in by simply typing in this URL.
So let's try this out.
I log out and then I'm accessing this URL again.
And you see that we get an rt error message
caused by the template trying to access the user object,
which is null.
And this is just a rather harmless implication
by not properly securing routes.
So we have to make sure that only authenticated users can
access routes where we assume that the user
has to be logged in to access them,
and we will implement it in the next video.