This document shows how to make this example.
Initial project setup
-
Open the command-line (Git Bash or Terminal).
-
Let’s create a new project
express --no-view Node5-Authentication
-
Go into the project folder you created. After this step, assume you are in this folder.
cd Node5-Authentication
-
From the command line, install dependencies listed in
package.json
. This command may take a while to run.npm install
Database setup
-
Install Node.js bindings to PostgreSQL .
npm install pg --save
-
We will need to define DATABASE_URL into
.env
to access the database.# Windows users only (replace password with the database password you created during installation) echo "DATABASE_URL='postgres://postgres:password@localhost/postgres'" > .env # Mac users echo "DATABASE_URL='postgres://localhost'" > .env
-
To read the
.env
file intoprocess.env
, installdotenv
:npm install dotenv --save
Authentication dependencies setup
-
Install
bcryptjs
. This encrypts plain text password. Reference: https://www.npmjs.com/package/bcryptjsnpm install bcryptjs --save
-
Install three packages
passport.js
. Passport is authentication middleware for Node.js (Reference: http://www.passportjs.org/)passport-local
: local authentication (not using third-party authentication, such as passport-google, passport-twitter, passport-github…)express-flash
: reference: https://www.npmjs.com/package/express-flashnpm install passport passport-local express-flash --save
-
Install session middleware. Authentication requires session middleware
npm install express-session --save
Create a table and insert rows
-
Connect to the local database: You will need to start Postgres first (Windows users: go to
git bash
and you will need to provide a password)# Windows users psql -U postgres # Mac users psql
-
Once connected, create a
user
table. Remember: NEVER store the password directly in the database.CREATE TABLE users ("id" serial primary key, username text, password text, fullname text, prefer text);
-
Insert a user (admin) with password hello (for TESTING purposes only):
INSERT INTO users (username,password,fullname,prefer) VALUES('admin','$2a$10$tXMKF036p0ZYIxF/cJEHauw/TFrcho4DXy41Kt12D3Lbnzr221hmK','Alan Turing','Prof');
How to create an encrypted password? Create a file
bcrypt.js
var bcrypt = require('bcryptjs'); var salt = bcrypt.genSaltSync(10); var hash = bcrypt.hashSync("hello", salt); console.log(salt); console.log(hash);
When you run bcrypt.js
, you will see encrypted 'hello'node bcrypt.js
Did you see it?
Let’s modify files
-
Modify
app.js
:var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); // add this line before var indexRouter = require('./routes/index'); // If not, you MAY have "password must be a string" error var env = require('dotenv').config(); var indexRouter = require('./routes/index'); // index.js var usersRouter = require('./routes/users'); // users.js var app = express(); // new stuff starts here var session = require('express-session'); // Flash is needed for passport middleware var flash = require('express-flash'); const Client = require('pg').Client; // create an instance from Client const client = new Client({ connectionString: process.env.DATABASE_URL }); client.connect(); //connect to database // javascript password encryption (https://www.npmjs.com/package/bcryptjs) var bcrypt = require('bcryptjs'); // authentication middleware var passport = require('passport'); // authentication locally (not using passport-google, passport-twitter, passport-github...) var LocalStrategy = require('passport-local').Strategy; passport.use(new LocalStrategy({ usernameField: 'username', // form field name passwordField: 'password' }, function(username, password, done) { client.query('SELECT * FROM users WHERE username = $1', [username], function(err, result) { if (err) { console.log("SQL error"); //next(err); return done(null,false, {message: 'sql error'}); } if (result.rows.length > 0) { let matched = bcrypt.compareSync(password, result.rows[0].password); if (matched) { console.log("Successful login, ", result.rows[0]); return done(null, result.rows[0]); } } console.log("Bad username or password"); // returning to passport // message is passport key return done(null, false, {message: 'Bad username or password'}); }); }) ); // Store user information into session passport.serializeUser(function(user, done) { //return done(null, user.id); return done(null, user); }); // Get user information out of session passport.deserializeUser(function(id, done) { return done(null, id); }); // Use the session middleware // configure session object to handle cookie // req.flash() requires sessions // secret: 'webDev' This is the secret used to sign the session ID cookie. // It should be a long, randomly-generated string to ensure security. app.use(session({ secret: 'WebDev', resave:false, saveUninitialized: true, })); app.use(flash()); app.use(passport.initialize()); app.use(passport.session()); // new stuff ends here app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/users', usersRouter); module.exports = app;
-
Modify
routes/users.js
:var express = require('express'); var router = express.Router(); /* // localhost:3000/users/ router.get('/', function(req, res, next) { res.send('respond with a resource'); }); */ // new stuff var path = require('path'); var env = require('dotenv').config(); // Connect to process.env.DATABASE_URL when your app initializes: // Read only reference value (const) // get only Client class from pg package const Client = require('pg').Client; // create an instance from Client const client = new Client({ connectionString: process.env.DATABASE_URL }); // connect to the DATABASE_URL client.connect(); var passport = require('passport'); var bcrypt = require('bcryptjs'); router.get('/logout', function(req, res, next){ req.logout(function(err) { if (err) { console.log("unable to logout:", err); return next(err); } }); //passport provide it res.redirect('/'); // Successful. redirect to localhost:3000/ }); function loggedIn(req, res, next) { if (req.user) { next(); // req.user exists, go to the next function (right after loggedIn) } else { res.redirect('/users/login'); // user doesn't exists redirect to localhost:3000/users/login } } router.get('/profile',loggedIn, function(req, res){ // req.user: passport middleware adds "user" object to HTTP req object res.sendFile(path.join(__dirname,'..', 'public','profile.html')); }); function notLoggedIn(req, res, next) { if (!req.user) { next(); } else { let prefer = req.user.prefer; res.redirect('/users/profile?name='+prefer); } } // localhost:3000/users/login router.get('/login', notLoggedIn, function(req, res){ //success is set true in sign up page res.sendFile(path.join(__dirname,'..', 'public','login.html')); }); // localhost:3000/users/login router.post('/login', // This is where authentication happens - app.js // authentication locally (not using passport-google, passport-twitter, passport-github...) passport.authenticate('local', { failureRedirect: 'login?message=Incorrect+credentials', failureFlash:true }), function(req, res,next) { let prefer = req.user.prefer; console.log("fullname: ", prefer); res.redirect('/users/profile?name='+prefer); // Successful. redirect to localhost:3000/users/profile }); router.get('/signup',function(req, res) { // If logged in, go to profile page if(req.user) { let prefer = req.user.prefer; return res.redirect('/users/profile?name='+prefer); } res.sendFile(path.join(__dirname,'..', 'public','signup.html')); }); function createUser(req, res, next){ var salt = bcrypt.genSaltSync(10); var password = bcrypt.hashSync(req.body.password, salt); client.query('INSERT INTO users (username, password, fullname, prefer) VALUES($1, $2, $3, $4)', [req.body.username, password,req.body.fullname,req.body.prefer], function(err, result) { if (err) { console.log("unable to query INSERT"); return next(err); // throw error to error.hbs. } console.log("User creation is successful"); res.redirect('/users/login?message=We+created+your+account+successfully!'); }); } router.post('/signup', function(req, res, next) { client.query('SELECT * FROM users WHERE username=$1',[req.body.username], function(err,result){ if (err) { console.log("sql error "); next(err); // throw error to error.hbs. } else if (result.rows.length > 0) { console.log("user exists"); res.redirect('/users/signup?error=User+exists'); } else { console.log("no user with that name"); createUser(req, res, next); } }); }); // new stuff ends here module.exports = router;
-
Modify
public/index.html
and save:<html> <head> <title>Express</title> <link rel="stylesheet" href="/stylesheets/style.css"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <nav class="menu"> <ul> <li><a href="/users/login">Log in</a> | </li> <li><a href="/users/signup">Sign up</a></li> </ul> </nav> <h1>Express</h1> <p>Welcome to Express</p> </body> </html>
-
Create
public/login.html
and save:<html> <head> <title>Express</title> <link rel="stylesheet" href="/stylesheets/style.css"> <link rel='stylesheet' href='/stylesheets/login.css' /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin="anonymous"></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin="anonymous"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin="anonymous"></script> <script src="/javascripts/showPassword.js" ></script> </head> <body> <nav class="menu"> <ul><a href="/">Back to Home</a></ul> </nav> <main class="text-center"> <!-- localhost:3000/users/login --> <form class="form-signin" method="post" action="login"> <div id="message"> <!-- This element's contents will be replaced with your component. --> </div> <h1 class="h3 mb-3 font-weight-normal">Sign in</h1> <input class="form-control" type="text" name="username" placeholder="User name or email address" required autofocus> <input class="form-control" type="password" name="password" id="password" placeholder="Password" required> <div class="checkbox mb-3"> <label><input type="checkbox" onclick="showPassword()"> Show password</label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> <div style="margin-top:15px ; border-top: 1px solid #888; padding-top:15px" > Don't have an account! <a href="signup" >Sign Up Here</a> </div> </form> </main> <!-- Load our React component. --> <script src="/javascripts/login.js" type="text/babel"></script> </body> </html>
-
Create
public/signup.html
and save:<html> <head> <title>Express</title> <link rel="stylesheet" href="/stylesheets/style.css"> <link rel='stylesheet' href='/stylesheets/login.css' /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin="anonymous"></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin="anonymous"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin="anonymous"></script> <script src="/javascripts/showPassword.js" ></script> </head> <body> <nav class="menu"> <ul><a href="/">Back to Home</a></ul> </nav> <main class="text-center"> <form class="form-signin" method="post" action="signup"> <div id="message"> <!-- This element's contents will be replaced with your component. --> </div> <h1 class="h3 mb-3 font-weight-normal">Create a new account</h1> <input class="form-control" type="text" name="fullname" placeholder="Full Name" required autofocus> <input class="form-control" type="text" name="prefer" placeholder="Preferred Name" required> <input class="form-control" type="text" name="username" placeholder="User name or email address" required> <input class="form-control" type="password" name="password" id="password" placeholder="Password" required> <div class="checkbox mb-3"> <label><input type="checkbox" onclick="showPassword()"> Show password</label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">Sign up</button> <div style="margin-top:15px ;border-top: 1px solid 888; padding-top:15px" > Already have an account? <a href="login" >Sign In Here</a> </div> </form> </main> <!-- Load our React component. --> <script src="/javascripts/signup.js" type="text/babel"></script> </body> </html>
-
Create
public/profile.html
and save:<html> <head> <title>Express</title> <link rel="stylesheet" href="/stylesheets/style.css"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <nav class="menu"> <ul> <li><a href="/">Back to Home</a> | </li> <li><a href="logout">Log out</a></li> </ul> </nav> <h1><div id="name"></div> </h1> <h2>Welcome to your profile!</h2> <h3>Feel free to make this page user friendly.</h3> <br> <script> const urlParams = new URLSearchParams(window.location.search); const name = urlParams.get('name'); if (name) { document.getElementById('name').innerHTML = name; } </script> </body> </html>
-
Create
public/javascripts/login.js
and save:const urlParams = new URLSearchParams(window.location.search); const message = urlParams.get('message'); if (message) { const LoginComponent = () => { return ( <> <p>{message}</p> </> ); }; const login = ReactDOM.createRoot(document.getElementById('message')); login.render(<LoginComponent />); }
-
Create
public/javascripts/signup.js
and save:const urlParams = new URLSearchParams(window.location.search); const message = urlParams.get('error'); if (message) { const SignupComponent = () => { return ( <> <p>{message}. Use different user name!</p> </> ); }; const signup = ReactDOM.createRoot(document.getElementById('message')); signup.render(<SignupComponent />); }
-
Create
public/javascripts/showPassword.js
and save:function showPassword() { var x = document.getElementById("password"); if (x.type === "password") { x.type = "text"; } else { x.type = "password"; } }
-
Modify
public/stylesheets/style.css
and save:body { padding: 50px; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } a { color: #00B7FF; } #message { color: red; } nav ul li { display: inline; list-style-type: none; } .menu {text-align: right;}
-
Create
public/stylesheets/login.css
and save:main { height: 100%; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; padding-top: 40px; padding-bottom: 40px; background-color: #f5f5f5; } .form-signin { width: 100%; max-width: 330px; padding: 15px; margin: auto; } .form-signin .checkbox { font-weight: 400; } .form-signin .form-control { position: relative; box-sizing: border-box; height: auto; padding: 10px; font-size: 16px; } .form-signin .form-control:focus { z-index: 2; }
-
Run the server to see the example project.
npm start
-
Go to http://localhost:3000/