How to Implement ACL with Passport using Node.js ?
Nodejs is an asynchronous event-driven JavaScript runtime, Node.js is designed to build scalable network applications.
Passportjs: Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more. It is widely used by developers to create secure and scalable web applications.
Steps to create ACL with passport using Node.js:
1: Create a Folder with a project name in any IDE (I’m using VS Code): See the below-mentioned folder structure.
Step 1: Open the terminal and type “npm init”. Keep on pressing enter for the default values since this command is used to set up a new npm package. Upon successful installation, it will create a package.json folder.
Step 2: For installing node dependencies, press “npm i express ejs”.
Step 3: For installing dev dependencies, press “npm i –save-dev nodemon dotenv”. Nodemon allows you to restart your server automatically every time anything changes and dotenv allows us to have environment variables that will store under .env file that we can load into the server.
Step 4: Make file “.env” and “.gitignore” as given in the below directory structure.
.gitignore contains those files that we don’t want to commit to our git repository. Here we have stored .env and node_modules since it contains only the dependencies and .env may contain sensitive information we don’t want to share with the world.
Step 5: The last thing that we need to do is set up package.json so that we can start the server. Edit the script section as follows:
Step 6: Now if you press “npm run devStart” in your terminal it will start the server. You should get something like this in your terminal.
2: Set up our basic express application with the below code in server.js.
Javascript
// STEP 1: Install all the necessary dependencies // add the dependencies with the help of require const express = require( 'express' ) const app = express() // To run our app on port 3000 app.listen(3000) |
But if we run our server after writing this piece of code, we will get the below page on localhost:300
It shows, Cannot GET/ because we haven’t set up any routes for our application yet. Let’s resolve this in step 3.
3: Add the above-mentioned code in server.js and make a folder named views with index.ejs in it.
4: Install another package using “npm i bcrypt”: bcrypt will allow us to hash passwords and compare hashed passwords to make sure that our application is completely secured.
server.js
Javascript
// Requiring all the necessary dependencies const express = require( 'express' ) const app = express() const bcrpyt = require( 'bcrypt' ) const users = [] // In order to use ejs set up view-engine app.set( 'view-engine' , 'ejs' ) app.use(express.urlencoded({ extended: false })) // Setting up home-page route app.get( '/' , (req, res) => { res.render( 'index.ejs' , { name: 'Janhvi' }) }) // Set up login page route app.get( '/login' , (req, res) => { res.render( 'login.ejs' ) }) // Set up register page route app.get( '/register' , (req, res) => { res.render( 'register.ejs' ) }) app.post( '/register' , async (req, res) => { // Using try catch block in order to // resolve errors try { // Hashing password with bcrypt const hashedPassword = await bcrpyt.hash(req.body.password, 10) users.push({ id: Date.now().toString(), name: req.body.name, email: req.body.email, password: hashedPassword }) res.redirect( '/login' ) } catch { res.redirect( '/register' ) } console.log(users) }) // To run our app on port 3000 app.listen(3000) |
login.ejs
Javascript
<!-- login page --> <h1>Login</h1> <!-- Make a simple login form with name, email and password fields make a submit button at end --> <form action= "/login" method= "POST" > <div> <label for = "name" >Name</label> <input type= "text" id= "name" name= "name" required> </div> <div> <label for = "email" >Email</label> <input type= "email" id= "email" name= "email" required> </div> <div> <label for = "password" >Password</label> <input type= "password" id= "password" name= "password" required> </div> <button type= "submit" >Login</button> </form> <a href= "/register" >Register</a> |
register.ejs
Javascript
<!-- Register page --> <h1>Register</h1> <!-- Make a simple register form with name, email and password fields make a submit button at end --> <form action= "/register" method= "POST" > <div> <label for = "name" >Name</label> <input type= "text" id= "name" name= "name" required> </div> <div> <label for = "email" >Email</label> <input type= "email" id= "email" name= "email" required> </div> <div> <label for = "password" >Password</label> <input type= "password" id= "password" name= "password" required> </div> <button type= "submit" >Register</button> </form> <a href= "/login" >Login</a> |
Desired output on, http://localhost:3000/login
http://localhost:3000/register
Upon giving an input, the console will look like this with a hashed password
5: Install passportjs using “npm i passport passport-local express-session express-flash”
Make a file “passport-config.js” in which you will store all the passport-related information. Now let’s have a look at the final version of all the codes with desired logic.
server.js
Javascript
// The below code is only suitable for // development not suitable for production if (process.env.NODE_ENV !== 'production' ) { require( 'dotenv' ).config() } // Requiring all the necessary dependencies const express = require( 'express' ) const app = express() const bcrypt = require( 'bcrypt' ) const passport = require( 'passport' ) const flash = require( 'express-flash' ) const session = require( 'express-session' ) const methodOverride = require( 'method-override' ) const initializePassport = require( './passport-config' ) initializePassport( passport, email => users.find(user => user.email === email), id => users.find(user => user.id === id) ) const users = [] // Setting up the view-engine in order // to use ejs in code further app.set( 'view-engine' , 'ejs' ) app.use(express.urlencoded({ extended: false })) app.use(flash()) app.use(session({ secret: process.env.SESSION_SECRET, resave: false , saveUninitialized: false })) app.use(passport.initialize()) app.use(passport.session()) app.use(methodOverride( '_method' )) // Setting up route logic for home page app.get( '/' , checkAuthenticated, (req, res) => { res.render( 'index.ejs' , { name: req.user.name }) }) // Setting up route logic for login page app.get( '/login' , checkNotAuthenticated, (req, res) => { res.render( 'login.ejs' ) }) app.post( '/login' , checkNotAuthenticated, passport.authenticate( 'local' , { successRedirect: '/' , failureRedirect: '/login' , failureFlash: true })) app.get( '/register' , checkNotAuthenticated, (req, res) => { res.render( 'register.ejs' ) }) // Hashinhg the passwords for each user // using bcrypt app.post( '/register' , checkNotAuthenticated, async (req, res) => { try { const hashedPassword = await bcrypt.hash(req.body.password, 10) users.push({ id: Date.now().toString(), name: req.body.name, email: req.body.email, password: hashedPassword }) res.redirect( '/login' ) } catch { res.redirect( '/register' ) } }) // To to login page upon pressing the // logout button app. delete ( '/logout' , (req, res) => { req.logOut() res.redirect( '/login' ) }) // If user is authenticated redirect to // next page otherwise redirect to login // page function checkAuthenticated(req, res, next) { if (req.isAuthenticated()) { return next() } res.redirect( '/login' ) } function checkNotAuthenticated(req, res, next) { if (req.isAuthenticated()) { return res.redirect( '/' ) } next() } // To run our app on port 3000 app.listen(3000) |
passport-config.js
Javascript
// Requiring all the necessary dependencies const LocalStrategy = require( 'passport-local' ).Strategy const bcrypt = require( 'bcrypt' ) // Add all the code related to passportjs to // the main initialize function function initialize(passport, getUserByEmail, getUserById) { const authenticateUser = async (email, password, done) => { const user = getUserByEmail(email) // If user is null return output // "no user with that email" if (user == null ) { return done( null , false , { message: 'No user with that email' }) } // try-catch block to check for correct password try { if (await bcrypt.compare(password, user.password)) { return done( null , user) } else { return done( null , false , { message: 'Password incorrect' }) } } catch (e) { return done(e) } } passport.use( new LocalStrategy( { usernameField: 'email' }, authenticateUser)) passport.serializeUser((user, done) => done( null , user.id)) passport.deserializeUser((id, done) => { return done( null , getUserById(id)) }) } // Exporting the initialize function module.exports = initialize |
index.ejs
HTML
<!-- Index page which will display the Hi user_name --> < h1 >Hi <%= name %></ h1 > <!-- Setting up a logout button in order to exit the page --> < form action = "/logout?_method=DELETE" method = "POST" > < button type = "submit" >Log Out</ button > </ form > |
login.ejs
HTML
<!-- login page --> < h1 >Login</ h1 > <!-- Adding embedded javascript code to check for errors --> <% if (messages.error) { %> <%= messages.error %> <% } %> <!-- Make a simple login form with name, email and password fields make a submit button at the end --> < form action = "/login" method = "POST" > < div > < label for = "email" >Email</ label > < input type = "email" id = "email" name = "email" required> </ div > < div > < label for = "password" >Password</ label > < input type = "password" id = "password" name = "password" required> </ div > < button type = "submit" >Login</ button > </ form > < a href = "/register" >Register</ a > |
register.ejs
HTML
<!-- Register page --> < h1 >Register</ h1 > <!-- Make a simple register form with name, email and password fields make a submit button at the end --> < form action = "/register" method = "POST" > < div > < label for = "name" >Name</ label > < input type = "text" id = "name" name = "name" required> </ div > < div > < label for = "email" >Email</ label > < input type = "email" id = "email" name = "email" required> </ div > < div > < label for = "password" >Password</ label > < input type = "password" id = "password" name = "password" required> </ div > < button type = "submit" >Register</ button > </ form > < a href = "/login" >Login</ a > |
6: Install another package using “npm i method-override” – The rest of the code remains the same.
The first page that will come up after starting the server is the login page.
If you have already registered, then login by using the same email and password.
If you haven’t registered and you tried to login then you will get this output.
Register yourself first and remember the email and password and then again try to login.
After successful registration and login you will get this on your page i.e. “Welcome to w3wiki your_name”.