Loading...
In this lesson we will learn how to authenticate users using Passport.js We will learn about popular authentication methods. We will learn what is passport and how to connect it to our express application. We will learn about different authentication we can use, and create our own authentication module and connect it to passport
Before diving to passport, lets talk about basic concepts refered to as authentication and authorization.
Authenticating a user is knowing who that user is.
Authorizing a user is knowing what that user can access and if he is allowed to access the thing he is now trying to access.
This means that in order to authorize a user we first need to authenticate him, before i allow a user to manipulate sensitive information i have to know who he is.
Passport is an authentication middleware for Node.js.
It's can be easily connected to any express app.
It's modular so you can connect different forms of authentication, the open source community offers alot of passport authentication modules you can connect to passport including: authenticate with google, with facebook, JWT, username and password, and others.
Since passport is modular and very popular, you will find community package to authenticate with every popular means today.
An authentication module that connects to passport is refered to as Strategy.
You can also easily create your own authentication module and connect it to passport.
We learned that for a route in express we can connect multiple listeners, and passport is using that fact to authenticate.
We are defining the routes the require authentication lets say those routes begin with /auth
We use the method passport.authenticate on the route. passport.authenticate Will try to authenticate using the strategy we chose and will call next after authentication to pass to the next listener.
If we didn't manage to authenticate it will return 401 and optionally redirect to an error page.
If managed to authenticate will will populate the req.user with details about the user.
So to simplify how passport is working, you can look at it like this:
app.get('/auth', function whatPassportIsDoing(req, res, next) {
req.user = 'i will populate the user with some data and then call next';
next(); //this will jump to the next listener and we will have req.user for that listener
});
to get the passport listener function above we call passport.authenticate
To use passport you have to do the following:
- install passport
- Install the strategy you want to use
- configure passport to use that strategy with passport.use
- everywhere you want to authenticate with the strategy just use passport.authenticate("name-of-strategy", {passport options})
Lets try and install passport and try to install it with a few strategies and combine it with express app.
the first strategy we will use is a custom one that we will create. Our custom strategy will look for a header in the request called hello and make sure that header is equal to world. on failed authentication we will redirect to error page. on success we will be able to access the restricted page. Lets start by creating an express application with 3 pages: login, restricted, error. We will use pug temlates for the views. First lets install express and pug
> npm install express pug --save
Create a file called app.js with the following code:
const express = require('express');
const path = require('path');
const app = express();
app.set('view engine', 'pug');
app.set('views', path.resolve(__dirname, 'views'));
app.get('/login', function(req, res) {
res.render('login');
})
app.get('/restricted', function(req, res) {
res.render('restricted');
})
app.get('/error', function(req, res) {
res.render('error');
})
app.listen(3001, function() {
console.log('we are now listening on port 3001');
});
In this code we simply created a new express application. Set the template engine to pug, and the views directory. Created 3 routes that render a template. where the login page is where the user should login and every user can view this page rather that user authenticated or not. The restricted only authenticated users can view. the error page we redirect if there is an authentication error. Create the views directory and create the following templates in that directory. login.pug
doctype html
html
body
h1 You can view this page even if you are not logged in
restricted.pug
doctype html
html
body
h1 Only authenticated users can view this.
error.pug
doctype html
html
body
h1 login error
those templates are simple and just display a message on the screen.
Lets install passport and the strategy we want to use. we want to use our own strategy, and to create a custom strategy on our own we can use the passport-custom strategy. lets use npm to install passport and passport-custom.
> npm install passport passport-custom --save
we need to connect passport to the strategy by calling passport.use In the app.js add the following code after configuration the views and view engine. also this code replace the route for the login and restricted:
const passport = require('passport');
const PassportCustom = require('passport-custom');
passport.use('custom', new PassportCustom((req, callback) => {
if (req.headers && req.headers.hello === 'world') {
callback(null, {firstName: 'foo', lastName: 'bar'});
} else {
callback(null, false)
}
}))
app.get('/login', passport.authenticate('custom', {session: false, failureRedirect: '/error'}), function(req, res) {
console.log(req.user);
res.render('login');
})
app.get('/restricted', passport.authenticate('custom', {session: false, failureRedirect: '/error'}), function(req, res) {
console.log(req.user);
res.render('restricted');
})
To make passport use our strategy, we need to create a new instance of it and place that instance in passport.use. When creating instance of a strategy we need to pass the constructor arguments based on the requirement of the constructor. In the custom passport strategy we need to pass a validate function, which gets the request and a callback. We need to call the callback where the first argumnet of the callback is an error, and the second is the user object or false if authentication fails. If authentication succeeds the user we are passing in the validate will be available in req.user. In the routes configuration we are placing a call to passport.authenticate which will redirect to the error page if authentication fails, otherwise will pass to the next listener with req.user populated. Try and request the login and restricted pages with the proper header and without the header. If requesting those pages without the hello header and you will be redirected to the error page.
When calling passport.authenticate we need to pass as first argument the strategy to use, and the second argument is options for passport, like where to aunticate if we fail, or if we want to use session. By default passport will establish a persistent login session, the idea is to only authenticate once, save the user or part of the user data in the session and not authenticate again. For this feature to work we need to install the middleware passport.initialize. passport.initialize will initialize session related stuff that passport will require. and also install the middleware passport.session. Passport session job is to check if there is a user in the session and if so populate the req.user with session data. passport.session will need to use the session middleware as well so we will need to install that before using passport.session. Lets try to modify our app to do the following: We would like to supply the proper header only in the login page, and after that we would like the login details to be saved in the session. If the session exists we would not need to provide the header again, this means that after accessing the login page we can access the welcome page without that header and still be logged in. In app.js add the following before passport.use where we installed our custom strategy.
const session = require('express-session');
app.use(session({
secret: 'nerdeez.com'
}));
app.use(passport.initialize());
app.use(passport.session());
to install the session for express we are using the express-session middleware which we will need to install
> npm install express-session --save
make sure to also remove the session: false from the passport.authenticate configuration.
the configuration to save the user in the session is turned on by default.
Usually for the login session we would not want to save all the user object in the session, but rather save the pk of the user and grab the user from the database.
For this use case passport provide us with two methods we would need to implement.
- First method is in charge of telling what will be saved in the session serializeUser
- Second method is in charge of grabbing what we saved in the session (like the pk) and grabbing the entire object from the database.
For our simple use case we would save the entire object in the session. Add the following after setting the passport middlewares in app.js
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
Now we are calling our authentication only on the login route, so youre login route will look the same as before:
app.get('/login', passport.authenticate('custom', {successRedirect: '/restricted', failureRedirect: '/error'}), function(req, res) {
console.log(req.user);
res.render('login');
});
In the restricted route we no longer need to authenticate, passport will add a method to the request object called isAuthenticated which will return true if we are logged in through the session. we simply need to create the following function to check if the user is logged in:
function isLoggedIn(req, res, next) {
if (req.isAuthenticated()) {
next();
} else {
res.redirect('/error');
}
}
The above function will use the isAuthenticated function and if falsy will redirect to the error page. Now to all of our restricted routes we need to pass a refrence to this function before the actual listener. So let's change the restricted route to look like this:
app.get('/restricted', isLoggedIn, function(req, res) {
console.log(req.user);
res.render('restricted');
})
before our listener we will check if the user is authenticated by calling the isLoggedIn function. Lets try another strategy.
This strategy is useful for authentication on a rest server.
The strategy works as follows, when the user logs in, the server returns a JWT token, every request the user is sending that requires authentication we need to send that token in the header in the following format:
Authorization: Bearer <token>
The JWT authentication is stateless like the rest protocol and thus do not require session usage.
First lets install the jwt strategy.
> npm install passport-jwt --save
when the user will log in, we will create a jwt token for that user. From that point the user will have to take that token and assign it to the authorization header on every request. We will create that token in the /restricted route, and print that token with the html we are creating in that route. Install the node JWT token which we will use to create our token.
> npm install jsonwebtoken --save
Now change the /restricted route to generate that token and send it to the template:
const jwt = require('jsonwebtoken');
app.get('/restricted', isLoggedIn, function(req, res) {
const token = jwt.sign(req.user, 'nerdeez.com');
res.render('restricted', {token});
})
we are taking the user object {firstName: 'foo', lastName: 'bar'} and creating a jwt token where this user is the payload. we are then sending that token to the template so change the restricted template to this:
doctype html
html
body
h1 Only authenticated users can view this.
p= `The token is: ${token}`
Now for jwt authentication, that token should be in our Authorization header. Now lets add the jwt strategy to our list of passport strategies. Below the other strategies add the following:
const JWTStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
passport.use('jwt', new JWTStrategy({
secretOrKey: 'nerdeez.com',
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()
}, function(payload, done) {
done(null, payload);
}))
similar to connecting other strategies we use the passport.use giving a name to that strategy and creating a new instace. For creating the instance the first argument is the option object, where we given the secret we used to sign our token, and we also instructed the strategy how the token will be passed (with the authorization header with the bearer schema). The second argument for the strategy constructor is a function that will be called with our payload and from that will need to return the user to the req.user. Usually we save in the payload the pk of the user and grab the full user from the db. This time we will just return the same payload as our user object.
Time to create the route and attach that strategy. In the app.js add the following:
app.get('/todo', passport.authenticate('jwt', {session: false}), function(req, res) {
res.json([
'finish article',
'create slides',
'finish bugeez next version'
])
});
we now have jwt authentication on the /todo route. Try to take the token you created in the restriced route and append it as a header in the brearer schema. Create a request to /todo with header: Authorization: bearer generated-toke and you should see the list of todo items. If you remove the header or place a wrong token you will not manage to view the list. JWT authentication has become super easy, and similar flow will be in your app. The user will log in with username and password, you will create a token and send it back, from now on the user will attach this token to all api requests.
We cover here the basic usage of passport and the basic flow is this:
- install passport and the strategy you want to use
- if the strategy is used once and then a session is used (most of the times) then install express session middleware
- initialize passport and install the passport.session middleware
- configure passport with the strategy you chose with passport.use
- connect passport.authenticate to the route you want to perform authentication (usually in one route and the rest we use the session)
- If you are using the session then proceed to restricted routes only if req.isAuthenticated returns true.