NodeJS Express User Login With JWT (No Database)

Welcome to a step-by-step tutorial on how to create a user login system in NodeJS. Yep, there are a ton of these “login tutorials” all over the Internet, so let’s do things a little differently in this one – There’s no database. So you guys who just want a simple login system can rest (somewhat) easier – Read on!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Notes NodeJS Login Useful Bits & Links
The End

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES

  • Download the code and unzip it into your project folder.
  • Open the command line, navigate to the project folder.
  • Run npm i bcryptjs express body-parser cookie-parser multer jsonwebtoken.
  • Run node 4-server.js and access http://localhost/login.
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

EXAMPLE CODE DOWNLOAD

Click here to download all the example source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

 

NODEJS USER LOGIN

All right, let us now get into the steps of building a user login system in NodeJS.

 

STEP 1) DUMMY HOME PAGE

1-home.html
<h1>Hello World!</h1>
<p>This page can be accessed by anyone.</p>

First, we start by creating a very simple dummy home page that can be accessed by anyone.

 

STEP 2) DUMMY ADMIN PAGE

2-admin.html
<h1>It Works!</h1>
<p>This page can only be accessed by admin.</p>

Then, another admin page that can only be accessed by users who are signed in.

 

STEP 3) LOGIN PAGE

THE HTML

3-login.html
<form id="login" onsubmit="return login()">
  <h1>LOGIN</h1>
  <input type="email" placeholder="Email" name="email" required value="jon@doe.com"/>
  <input type="password" placeholder="Password" name="password" required value="12345"/>
  <input type="submit" value="Sign In"/>
</form>

This login page is self-explanatory, just an HTML form with the email and password.

 

 

THE JAVASCRIPT

assets/login.js
function login () {
  // (A) GET EMAIL + PASSWORD
  var data = new FormData(document.getElementById("login"));
 
  // (B) AJAX REQUEST
  fetch("/in", { method:"POST", body:data })
  .then((res) => { return res.text(); })
  .then((txt) => {
    if (txt=="OK") { location.href = "../admin"; }
    else { alert(txt); }
  })
  .catch((err) => {
    alert("Server error - " + err.message);
    console.error(err);
  });
  return false;
}

To process the login, we send a fetch request to /in with the email and password.

 

STEP 4) NODE SERVER

INITIALIZE

4-server.js
// (A) INITIALIZE
// (A1) LOAD REQUIRED MODULES
// npm i bcryptjs express body-parser cookie-parser multer jsonwebtoken
const bcrypt = require("bcryptjs"),
      path = require("path"),
      express = require("express"),
      bodyParser = require("body-parser"),
      cookieParser = require("cookie-parser"),
      multer = require("multer"),
      jwt = require("jsonwebtoken");
 
// (A2) EXPRESS + MIDDLEWARE
const app = express();
app.use(multer().array());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

So far so good? The first section of the NodeJS should be self-explanatory too.

  • (A1) Load all the required modules.
  • (A2) Initialize Express and use all the required middleware.

 

 

USER ACCOUNTS

4-server.js
// (B) USER ACCOUNTS - AT LEAST ENCRYPT YOUR PASSWORDS!
// bcrypt.hash("PASSWORD", 8, (err, hash) => { console.log(hash); });
const users = {
  "jon@doe.com" : "$2a$08$g0ZKZhiA97.pyda3bsdQx.cES.TLQxxKmbvnFShkhpFeLJTc6DuA6"
};

Yep, there’s no database. So the only way is to keep the users in an object… But at least have the human decency to encrypt the password, just run a simple bcrypt.hash().

 

JSON WEB TOKEN

4-server.js
// (C) JSON WEB TOKEN
// (C1) SETTINGS - CHANGE TO YOUR OWN!
const jwtKey = "YOUR-SECRET-KEY",
      jwtIss = "YOUR-NAME",
      jwtAud = "site.com",
      jwtAlgo = "HS512";
 
// (C2) GENERATE JWT TOKEN
jwtSign = (email) => {
  // RANDOM TOKEN ID
  let char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&_-", rnd = "";
  for (let i=0; i<16; i++) {
    rnd += char.charAt(Math.floor(Math.random() * char.length));
  }
 
  // UNIX NOW
  let now = Math.floor(Date.now() / 1000);
 
  // SIGN TOKEN
  return jwt.sign({
    iat : now, // ISSUED AT - TIME WHEN TOKEN IS GENERATED
    nbf : now, // NOT BEFORE - WHEN THIS TOKEN IS CONSIDERED VALID
    exp : now + 3600, // EXPIRY - 1 HR (3600 SECS) FROM NOW IN THIS EXAMPLE
    jti : rnd,
    iss : jwtIss, // ISSUER
    aud : jwtAud, // AUDIENCE
    // WHATEVER ELSE YOU WANT TO PUT
    data : { email : email }
  }, jwtKey, { algorithm: jwtAlgo });
};
 
// (C3) VERIFY TOKEN
jwtVerify = (cookies) => {
  if (cookies.JWT===undefined) { return false; }
  try {
    let decoded = jwt.verify(cookies.JWT, jwtKey);
    // DO WHATEVER EXTRA CHECKS YOU WANT WITH DECODED TOKEN
    // console.log(decoded);
    return true;
  } catch (err) { return false; }
}

This next section deals with the JSON Web Token (JWT). For you guys who do not know what this does, in layman terms:

  • (C2) On successful login, we set an encrypted JWT cookie token in the user’s browser.
  • (C3) On subsequent visits to the website, we verify the JWT cookie and check if the user has access rights.

If you don’t want to deal with the technical parts, at least change the settings (C1) to your own:

  • jwtKey Generate your own random secret key.
  • jwtIss The “issuer”. Commonly the company or domain name.
  • jwtAud The “audience”. Your domain name, where the token is supposed to be valid for.

 

 

EXPRESS HTTP

4-server.js
// (D) EXPRESS HTTP
// (D1) STATIC ASSETS
app.use("/assets", express.static(path.join(__dirname, "assets")))
 
// (D2) HOME PAGE - OPEN TO ALL
app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "/1-home.html"));
});
 
// (D3) ADMIN PAGE - REGISTERED USERS ONLY
app.get("/admin", (req, res) => {
  if (jwtVerify(req.cookies)) {
    res.sendFile(path.join(__dirname, "/2-admin.html"));
  } else {
    res.redirect("../login");
  }
});
  • (D1) Serve all files in the public /assets as-it-is.
  • (D2) As explained earlier, accessing http://localhost/ will serve the home page; It is open to everyone.
  • (D3) But for the admin page http://localhost/admin, the user needs to be logged in. Take note of jwtVerify() here, we redirect unverified users to the login page instead.

 

LOGIN

4-server.js
// (D4) LOGIN PAGE
app.get("/login", (req, res) => {
  if (jwtVerify(req.cookies)) {
    res.redirect("../admin");
  } else {
    res.sendFile(path.join(__dirname, "/3-login.html"));
  }
});
 
// (D5) LOGIN ENDPOINT
app.post("/in", async (req, res) => {
  let pass = users[req.body.email] !== undefined;
  if (pass) {
    pass = await bcrypt.compare(req.body.password, users[req.body.email]);
  }
  if (pass) {
    res.cookie("JWT", jwtSign(req.body.email));
    res.status(200);
    res.send("OK");
  } else {
    res.status(200);
    res.send("Invalid user/password");
  }
});
  • (D4) Serve the login page at http://localhost/login. Take note of jwtVerify() again, we redirect users who are already signed in to the admin page.
  • (D5) Remember step 3? The login page will send the email and password to this endpoint. We simply check the email/password against const users, and generate the JWT cookie if it is valid.

 

 

LOGOUT

4-server.js
// (D6) LOGOUT ENDPOINT
app.post("/out", (req, res) => {
  res.clearCookie();
  res.status(200);
  res.send("OK");
});

To log the user out, we simply unset the JWT cookie (or lazily clean out all the cookies).

P.S. Before the “master code ninjas” start to scream “this is stupid”, we can just clear the client-side document.cookie – I will highly recommend setting the httpOnly flag on the JWT cookie in the above login sequence. This endpoint still serves its purpose.

 

LAUNCH

4-server.js
// (E) GO!
app.listen(80);

Start the server and listen to port 80 (common HTTP).

 

USEFUL BITS & LINKS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

WHAT’S NEXT?

This is pretty much a working example as-it-is. So go ahead and build all your HTML pages, set more of your own endpoints with app.get() and app.post().

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Leave a Comment

Your email address will not be published.