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
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
<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
<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
<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
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
// (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
// (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
// (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
// (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 ofjwtVerify()
here, we redirect unverified users to the login page instead.
4E) LOGIN
// (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 ofjwtVerify()
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 theJWT
cookie if it is valid.
4F) LOGOUT
// (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
// (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
- ExpressJS
- JSON Web Token – GitHub
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!