JWT User Login With NodeJS Express (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!

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Here is the download link to the example code, so you don’t have to copy-paste everything.

 

EXAMPLE CODE DOWNLOAD

Source code on GitHub Gist

Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

SORRY FOR THE ADS...

But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.

Buy Me A Coffee Code Boxx eBooks

 

 

NODEJS USER LOGIN

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

 

 

QUICK SETUP

Run npm i bcryptjs express body-parser cookie-parser multer jsonwebtoken to install the required modules.

 

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
<form method="post" action="/out">
  <h1>It Works!</h1>
  <p>This page can only be accessed by admin.</p>
  <input type="submit" value="Sign Out">
</form>

Then, an admin page that can only be accessed by users who are signed in. Just keep a small note on the signout here – We only need to POST to /out to sign out.

 

STEP 3) LOGIN PAGE

3A) THE HTML

3-login.html
<form id="login" onsubmit="return login()">
  <label>Email</label>
  <input type="email" name="email" required value="jon@doe.com">
  <label>Password</label>
  <input type="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.

 

 

3B) 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 => res.text())
  .then(txt => {
    if (txt=="OK") { location.href = "../admin"; }
    else { alert(txt); }
  })
  .catch(err => 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

4A) 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.

 

 

4B) 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().

 

4C) 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 => {
  // (C2-1) RANDOM TOKEN ID
  let char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&_-", rnd = "";
  for (let i=0; i<16; i++) {
    rnd += char.charAt(Math.floor(Math.random() * char.length));
  }
 
  // (C2-2) UNIX NOW
  let now = Math.floor(Date.now() / 1000);
 
  // (C2-3) 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, // random token id
    iss : jwtIss, // issuer
    aud : jwtAud, // audience
    data : { email : email } // whatever else you want to put
  }, 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’s 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.

 

 

4D) 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.

 

4E) 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.

 

 

4F) LOGOUT

4-server.js
// (D6) LOGOUT ENDPOINT
app.post("/out", (req, res) => {
  res.clearCookie("JWT");
  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.

 

4G) LAUNCH

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

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

 

EXTRAS

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!