This tutorial is out of date and no longer maintained.
Note: This article is part of our Easy Node Authentication series.
Authentication and logins in Node can be a complicated thing. Actually logging in for any application can be a pain. This article series will deal with authenticating in your Node application using the package Passport.
Note: Updates
Edit 11/18/2017: Updated to reflect Facebook API changes. Updating dependencies in package.json
.
Edit #1: Changed password hashing to be handled inside user model and asynchronously.
Edit #2: Changed password hashing to be explicitly called. Helps with future tutorials.
We will build an application that will have:
Enough chit-chat. Let’s dive right into a completely blank Node application and build our entire application from scratch.
Here’s what we’ll be building:
And after a user has logged in with all their credentials:
For this article, we’ll be focusing on setup and only local logins and registrations/signups. Since this is the first article and also deals with setting up our application, it will probably be one of the longer ones if not the longest. Sit tight for the duration of your flight.
To set up our base Node application, we’ll need a few things. We’ll set up our npm packages, node application, configuration files, models, and routes.
- app
------ models
---------- user.js <!-- our user model -->
------ routes.js <!-- all the routes for our application -->
- config
------ auth.js <!-- will hold all our client secret keys (facebook, twitter, google) -->
------ database.js <!-- will hold our database connection settings -->
------ passport.js <!-- configuring the strategies for passport -->
- views
------ index.ejs <!-- show our home page with login links -->
------ login.ejs <!-- show our login form -->
------ signup.ejs <!-- show our signup form -->
------ profile.ejs <!-- after a user logs in, they will see their profile -->
- package.json <!-- handle our npm packages -->
- server.js <!-- setup our application -->
Go ahead and create all those files and folders and we’ll fill them in as we go along.
package.json
We are going to install all the packages needed for the entire tutorial series. This means we’ll install all the packages needed for passport
local
, facebook
, twitter
, google
, and the other things we need. It’s all commented out so you know what each does.
{
"name": "node-authentication",
"main": "server.js",
"dependencies" : {
"express" : "~4.14.0",
"ejs" : "~2.5.2",
"mongoose" : "~4.13.1",
"passport" : "~0.3.2",
"passport-local" : "~1.0.0",
"passport-facebook" : "~2.1.1",
"passport-twitter" : "~1.0.4",
"passport-google-oauth" : "~1.0.0",
"connect-flash" : "~0.1.1",
"bcrypt-nodejs" : "latest",
"morgan": "~1.7.0",
"body-parser": "~1.15.2",
"cookie-parser": "~1.4.3",
"method-override": "~2.3.6",
"express-session": "~1.14.1"
}
}
Most of these are pretty self-explanatory.
I use bcrypt-nodejs
instead of bcrypt
since it is easier to set up in Windows.
Now that we have all of our dependencies ready to go, let’s go ahead and install them:
npm install
With all of our packages ready to go, let’s set up our application in server.js
.
server.js
Let’s make all our packages work together nicely. Our goal is to set up this file and try to have it bootstrap our entire application. We’d like to not go back into this file if it can be helped. This file will be the glue for our entire application.
// set up ======================================================================
// get all the tools we need
var express = require('express');
var app = express();
var port = process.env.PORT || 8080;
var mongoose = require('mongoose');
var passport = require('passport');
var flash = require('connect-flash');
var morgan = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
var configDB = require('./config/database.js');
// configuration ===============================================================
mongoose.connect(configDB.url); // connect to our database
// require('./config/passport')(passport); // pass passport for configuration
// set up our express application
app.use(morgan('dev')); // log every request to the console
app.use(cookieParser()); // read cookies (needed for auth)
app.use(bodyParser()); // get information from html forms
app.set('view engine', 'ejs'); // set up ejs for templating
// required for passport
app.use(session({ secret: 'ilovescotchscotchyscotchscotch' })); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
// routes ======================================================================
require('./app/routes.js')(app, passport); // load our routes and pass in our app and fully configured passport
// launch ======================================================================
app.listen(port);
console.log('The magic happens on port ' + port);
We are going to comment out our passport
configuration for now. We’ll uncomment it after we create that config/passport.js
file.
The path of our passport
object is important to note here. We will create it at the very beginning of the file with var passport = require('passport');
. Then we pass it into our config/passport.js
file for it to be configured. Then we pass it to the app/routes.js
file for it to be used in our routes.
Now with this file, we have our application listening on port 8080. All we have to do to start up our server is:
node server.js
Then when we visit http://localhost:8080
we will see our application. (Not really right this moment since we have some more set up to do)
Auto Refreshing: By default, Node.js doesn’t automatically refresh our server every time we change files. To do that we’ll use nodemon. Just install with: npm install -g nodemon
and use with: nodemon server.js
.
Now, this won’t do much for our application since we don’t have our database configuration, routes, user model, or passport configuration set up. Let’s do the database and routes now.
config/database.js
We already are calling this file in server.js
. Now we just have to set it up.
module.exports = {
'url' : 'your-settings-here' // looks like mongodb://<user>:<pass>@mongo.onmodulus.net:<port>
};
Fill this in with your own database. If you don’t have a MongoDB database lying around, I would suggest going to Modulus.io and grabbing one. Once you sign up (and you get a $15 credit for signing up), you can create your database, grab its connection URL, and place it in this file.
You can also install MongoDB locally and use a local database. You can find instructions here: An Introduction to MongoDB.
app/routes.js
We will keep our routes simple for now. We will have the following routes:
/
)/login
)/signup
) module.exports = function(app, passport) {
// =====================================
// HOME PAGE (with login links) ========
// =====================================
app.get('/', function(req, res) {
res.render('index.ejs'); // load the index.ejs file
});
// =====================================
// LOGIN ===============================
// =====================================
// show the login form
app.get('/login', function(req, res) {
// render the page and pass in any flash data if it exists
res.render('login.ejs', { message: req.flash('loginMessage') });
});
// process the login form
// app.post('/login', do all our passport stuff here);
// =====================================
// SIGNUP ==============================
// =====================================
// show the signup form
app.get('/signup', function(req, res) {
// render the page and pass in any flash data if it exists
res.render('signup.ejs', { message: req.flash('signupMessage') });
});
// process the signup form
// app.post('/signup', do all our passport stuff here);
// =====================================
// PROFILE SECTION =====================
// =====================================
// we will want this protected so you have to be logged in to visit
// we will use route middleware to verify this (the isLoggedIn function)
app.get('/profile', isLoggedIn, function(req, res) {
res.render('profile.ejs', {
user : req.user // get the user out of session and pass to template
});
});
// =====================================
// LOGOUT ==============================
// =====================================
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
};
// route middleware to make sure a user is logged in
function isLoggedIn(req, res, next) {
// if user is authenticated in the session, carry on
if (req.isAuthenticated())
return next();
// if they aren't redirect them to the home page
res.redirect('/');
}
app.post: For now, we will comment out the routes for handling the form POST. We do this since passport
isn’t set up yet.
req.flash: This is the connect-flash way of getting flashdata in the session. We will create the loginMessage
inside our passport
configuration.
isLoggedIn: Using route middleware, we can protect the profile section route. A user has to be logged in to access that route. Using the isLoggedIn
function, we will kick a user back to the home page if they try to access http://localhost:8080/profile
and they are not logged in.
Logout: We will handle log out by using req.logout()
provided by passport. After logging out, redirect the user to the home page.
With our server running, we can visit our application in our browser athttp://localhost:8080
. Once again, we won’t see much since we haven’t made our views. Let’s go do that now. (We’re almost to the authentication stuff, I promise).
views/index.ejs
, views/login.ejs,
views/signup.ejs
Here we’ll define our views for our home page, login page, and signup/registration page.
views/index.ejs
Our home page will just show links to all our forms of authentication.
<!doctype html>
<html>
<head>
<title>Node Authentication</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->
<style>
body { padding-top:80px; }
</style>
</head>
<body>
<div class="container">
<div class="jumbotron text-center">
<h1><span class="fa fa-lock"></span> Node Authentication</h1>
<p>Login or Register with:</p>
<a href="/login" class="btn btn-default"><span class="fa fa-user"></span> Local Login</a>
<a href="/signup" class="btn btn-default"><span class="fa fa-user"></span> Local Signup</a>
</div>
</div>
</body>
</html>
Now if we visit our app in our browser, we’ll have a site that looks like this:
Here are the views for our login and signup pages also.
views/login.ejs
<!doctype html>
<html>
<head>
<title>Node Authentication</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->
<style>
body { padding-top:80px; }
</style>
</head>
<body>
<div class="container">
<div class="col-sm-6 col-sm-offset-3">
<h1><span class="fa fa-sign-in"></span> Login</h1>
<!-- show any messages that come back with authentication -->
<% if (message.length > 0) { %>
<div class="alert alert-danger"><%= message %></div>
<% } %>
<!-- LOGIN FORM -->
<form action="/login" method="post">
<div class="form-group">
<label>Email</label>
<input type="text" class="form-control" name="email">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-warning btn-lg">Login</button>
</form>
<hr>
<p>Need an account? <a href="/signup">Signup</a></p>
<p>Or go <a href="/">home</a>.</p>
</div>
</div>
</body>
</html>
views/signup.ejs
<!doctype html>
<html>
<head>
<title>Node Authentication</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->
<style>
body { padding-top:80px; }
</style>
</head>
<body>
<div class="container">
<div class="col-sm-6 col-sm-offset-3">
<h1><span class="fa fa-sign-in"></span> Signup</h1>
<!-- show any messages that come back with authentication -->
<% if (message.length > 0) { %>
<div class="alert alert-danger"><%= message %></div>
<% } %>
<!-- LOGIN FORM -->
<form action="/signup" method="post">
<div class="form-group">
<label>Email</label>
<input type="text" class="form-control" name="email">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-warning btn-lg">Signup</button>
</form>
<hr>
<p>Already have an account? <a href="/login">Login</a></p>
<p>Or go <a href="/">home</a>.</p>
</div>
</div>
</body>
</html>
Finally! We have finally set up our application and have gotten to the authentication part. Don’t worry. The rest of the authentication articles in this tutorial series will use the same base so we won’t have to set up our application again.
So far we have installed our packages, set up our application, connected to our database, created our routes, and created our views.
Now we will create our user model, configure passport for local authentication, and use our configured passport
to process our login/signup forms.
We will create our user model for the entire tutorial series. Our user will have the ability to be linked to multiple social accounts and to a local account. For local accounts, we will be keeping email and password. For the social accounts, we will be keeping their id, token, and some user information.
You can change these fields out to be whatever you want. You can authenticate locally using username and password (passport-local
actually uses username by default but we’ll change that to email).
// load the things we need
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
// define the schema for our user model
var userSchema = mongoose.Schema({
local : {
email : String,
password : String,
},
facebook : {
id : String,
token : String,
name : String,
email : String
},
twitter : {
id : String,
token : String,
displayName : String,
username : String
},
google : {
id : String,
token : String,
email : String,
name : String
}
});
// methods ======================
// generating a hash
userSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
// checking if password is valid
userSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
// create the model for users and expose it to our app
module.exports = mongoose.model('User', userSchema);
Our model is done. We will be hashing our password within our user model before it saves to the database. This means we don’t have to deal with generating the hash ourselves. It is all handled nicely and neatly inside our user model.
Let’s move on to the important stuff of this article: authenticating locally!
All the configuration for passport
will be handled in config/passport.js
. We want to keep this code in its own file away from our other main files like routes or the server file. I have seen some implementations where passport
will be configured in random places. I believe having it in this config file will keep your overall application clean and concise.
So far, we have created our passport
object in server.js
, and then we pass it to our config/passport.js
file. This is where we configure our Strategy for local
, facebook
, twitter
, and google
. This is also the file where we will create the serializeUser
and deserializeUser
functions to store our user in session.
I would highly recommend going to read the passport docs to understand more about how the package works.
We will be handling login and signup in config/passport.js
. Let’s look at signup first.
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
// load up the user model
var User = require('../app/models/user');
// expose this function to our app using module.exports
module.exports = function(passport) {
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// required for persistent login sessions
// passport needs ability to serialize and unserialize users out of session
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
};
We have now provided a strategy to passport
called local-signup. We will use this strategy to process our signup form. Let’s open up our app/routes.js
and handle the POST for our signup form.
...
// process the signup form
app.post('/signup', passport.authenticate('local-signup', {
successRedirect : '/profile', // redirect to the secure profile section
failureRedirect : '/signup', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
...
That’s all the code we need for the route. All of the heavy-duty stuff lives inside of config/passport.js
. All we have to set here is where our failures and successes get redirected. Super clean.
There is also much more you can do with this. Instead of specifying a successRedirect
, you could use a callback and take more control over how your application works. Here is a great stackoverflow answer on error handling. It explains how to use done()
and how to be more specific with your handling of a route.
With our passport
config finally laid out, we can uncomment that line in our server.js
. This will load our config and then we can use our signup form.
...
// uncomment this line
require('./config/passport')(passport); // pass passport for configuration
...
Now that we have passport
, our routes, and our redirects in place, let’s go ahead and test our signup form. In your browser, go to http://localhost:8080/signup
and fill out your form.
If all goes according to plan, you should be logged in, your user saved in the session, and you are redirected to the /profile
page (the profile page will show nothing right now since we haven’t defined that view).
If we look in our database, we’ll also see our user sitting there cozily with all the credentials we created for him.
Exploring Your Database: I use Robomongo to see what’s in my database. Just download it and connect to your database to see your new users after they signup!
With users able to sign up, let’s give them a way to log in.
This will be very similar to the signup strategy. We’ll add the strategy to our config/passport.js
and the route in app/routes.js
.
...
// =========================================================================
// LOCAL LOGIN =============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) { // callback with email and password from our form
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error before anything else
if (err)
return done(err);
// if no user is found, return the message
if (!user)
return done(null, false, req.flash('loginMessage', 'No user found.')); // req.flash is the way to set flashdata using connect-flash
// if the user is found but the password is wrong
if (!user.validPassword(password))
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata
// all is well, return successful user
return done(null, user);
});
}));
};
We have now provided a strategy to passport
called local-login. We will use this strategy to process our login form. We can check if a user exists, if the password is wrong, and set flash data to show error messages. Let’s open up our app/routes.js
and handle the POST for our login form.
...
// process the login form
app.post('/login', passport.authenticate('local-login', {
successRedirect : '/profile', // redirect to the secure profile section
failureRedirect : '/login', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
...
If you try to log in with a user email that doesn’t exist in our database, you will see the error. The same goes for if your password is wrong.
views/profile.ejs
Now we have functional signup and login forms. If a user is successful in authenticating they will be redirected to the profile page. If they are not successful, they will go home. The last thing we need to do is make our profile page so that those that are lucky enough to signup (all of us?) will have an exclusive place of our site all to themselves.
<!doctype html>
<html>
<head>
<title>Node Authentication</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">
<style>
body { padding-top:80px; word-wrap:break-word; }
</style>
</head>
<body>
<div class="container">
<div class="page-header text-center">
<h1><span class="fa fa-anchor"></span> Profile Page</h1>
<a href="/logout" class="btn btn-default btn-sm">Logout</a>
</div>
<div class="row">
<!-- LOCAL INFORMATION -->
<div class="col-sm-6">
<div class="well">
<h3><span class="fa fa-user"></span> Local</h3>
<p>
<strong>id</strong>: <%= user._id %><br>
<strong>email</strong>: <%= user.local.email %><br>
<strong>password</strong>: <%= user.local.password %>
</p>
</div>
</div>
</div>
</div>
</body>
</html>
After a user logs in, they can see all their information. It is grabbed from the session and passed to our view in the app/routes.js
file. We will also provide a link to log out.
There you have it! We’ve built a brand new application from scratch and have the ability to let users signup/register and log in. We even have support for flash messages, hashing passwords, and requiring a login for some sections of our site using route middleware.
Coming up next we’ll be looking at how to take this same structure, and use passport
to authenticate with Facebook, Twitter, and Google. After that, we’ll look at how we can get all these things working together in the same application. Users will be able to log in with one type of account, and then link their other accounts.
As always, if you see any ways to improve this or need any clarification, sound off in the comments and we’ll respond pretty close to immediately… pretty close.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!