Web Authentication (Example in Node JS)
Parsa Hejabi

I'm Parsa Hejabi 👋🏻

- I was Software Engineer at Digikala for one year
- Studying Computer Engineering
- Full-Stack developer
Special Thanks to Randall Degges
@rdegges

Path
and
journey...
Terminologies
Auth
Auth
0x01
Authentication
Authorization
VS

Authentication
Authentication is the act of validating that users are who they claim to be. Passwords are the most common authentication factor. if a user enters the correct password, the system assumes the identity is valid and grants access. Biometrics, pins, ...
Authorization
Authorization in system security is the process of giving the user permission to access a specific resource or function. Giving someone permission to download a particular file on a server or providing individual users with administrative access to an application are good examples.
HTTP
0x02
Hypertext Transfer Protocol

HTTP is stateless
HTTP is a "stateless" protocol which means each time a client retrieves a Webpage, the client opens a separate connection to the Web server and the server automatically does not keep any record of previous client request.
REGISTER
LOGIN
DASHBOARD
0x00 -
Setup Project
Prerequisites
- Node https://nodejs.org/en/
- Mongo https://www.mongodb.com/

$ npm init -y
$ npm install express
$ npm install pug
- Make a new npm package
- Install dependencies
$ mkdir views
$ touch server.js
$ touch views/base.pug
$ touch views/index.pug
$ touch views/register.pug
$ touch views/login.pug
$ touch views/dashboard.pug
Prepare the application
block vars
doctype html
html
head
title SS-Auth | #{title}
body
block body
-> views/base.pug
<!DOCTYPE html>
<html>
<head>
<title>SS-Auth | </title>
</head>
<body>
</body>
</html>
extends base
block vars
- var title = "Home"
block body
h1 SS-Auth!
p.
Welcome to the SS-Auth! Please #[a(href="/register") register] or #[a(href="/login") login] to continue.
-> views/index.pug
extends base
block vars
- var title = "Register"
block body
h1 Create an Account
form(method="post")
span First Name:
input(type="text", name="firstName", required=true)
br
span Last Name:
input(type="text", name="lastName", required=true)
br
span Email:
input(type="email", name="email", required=true)
br
span Password:
input(type="password", name="password", required=true)
br
input(type="submit")
-> views/register.pug
extends base
block vars
- var title = "Login"
block body
h1 Log Into Your Account
if error
p ERROR: #{error}
form(method="post")
span Email:
input(type="email", name="email", required=true)
br
span Password:
input(type="password", name="password", required=true)
br
input(type="submit")
-> views/login.pug
extends base
block vars
- var title = "Dashboard"
block body
h1 Dashboard
p.
Welcome to your dashboard! You are now logged in.
-> views/dashboard.pug
Create Basic Web App with Express
- Define routes
- Render templates
- Start server
const express = require("express");
let app = express();
app.set("view engine", "pug");
app.get("/", (req, res) => {
res.render("index");
});
app.get("/register", (req, res) => {
res.render("register");
});
app.get("/login", (req, res) => {
res.render("login");
});
app.get("/dashboard", (req, res) => {
res.render("dashboard");
});
app.listen(3000);
-> server.js
DEMO
0x01 -
Forms
HTML!
<form method="post">
First Name: <input type="text" name="firstName" required/>
Last Name: <input type="text" name="lastName" required/>
Email: <input type="email" name="email" required/>
Password: <input type="password" name="password" required/>
<input type="submit"/>
</form>
HTML!
<form method="post">
First Name: <input type="text" name="firstName" required/>
Last Name: <input type="text" name="lastName" required/>
Email: <input type="email" name="email" required/>
Password: <input type="password" name="password" required/>
<input type="submit"/>
</form>
Grab form data
$ npm install body-parser
// server.js
const bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({
extended: false
}));
app.post("/register", (req, res) => {
res.json(req.body);
});
{
"firstName": "Parsa",
"lastName": "Hejabi",
"email": "parsahejabi@parsahejabi.com",
"password": "longlongstring"
}
0x02-
Database
Mongo

$ mongo
MongoDB shell version v4.2.0
Mongo vs SQL terms
Mongo Basics
> use test;
switched to db test
> show collections;
> db.users.insert({ email: "parsahejabi@parsahejabi.com" });
WriteResult({ "nInserted" : 1 })
> db.users.find()
{ "_id" : ObjectId("5f21e7536902e4210694c7bf"), "email" : "parsahejabi@parsahejabi.com" }
Mongo Basics
> use test;
switched to db test
> show collections;
> db.users.insert({ email: "parsahejabi@parsahejabi.com" });
WriteResult({ "nInserted" : 1 })
> db.users.find()
{ "_id" : ObjectId("5f21e7536902e4210694c7bf"), "email" : "parsahejabi@parsahejabi.com" }
Mongoose ORM
$ npm install mongoose
// server.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true, useUnifiedTopology: true});
Mongoose model
// name, schema, collection
let User = mongoose.model(
"User",
new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
}),
"User"
);
Creating users
app.post("/register", (req, res) => {
let user = new User(req.body);
user.save((err) => {
if (err) {
let error = "Something bad happened! Please try again.";
if (err.code === 11000) {
error = "That email is already taken, please try another.";
}
return res.render("register", { error: error });
}
res.redirect("/dashboard");
});
});
Creating users
app.post("/register", (req, res) => {
let user = new User(req.body);
user.save((err) => {
if (err) {
let error = "Something bad happened! Please try again.";
if (err.code === 11000) {
error = "That email is already taken, please try another.";
}
return res.render("register", { error: error });
}
res.redirect("/dashboard");
});
});
{
"firstName": "Parsa",
"lastName": "Hejabi",
"email": "parsahejabi@parsahejabi.com",
"password": "longlongstring"
}
Check the Database
> db.users.find()
{
"_id" : ObjectId("5f21f14131eba444d3f7fb60"),
"firstName" : "Parsa",
"LastName" : "Hejabi",
"email" : "parsahejabi@parsahejabi.com",
"password" : "longlongstring",
"__v" : 0
}
Authenticating Users!
app.post("/login", (req, res) => {
User.findOne({ email: req.body.email }, (err, user) => {
if (err || !user || req.body.password !== user.password) {
return res.render("login", {
error: "Incorrect email / password."
});
}
res.redirect("/dashboard");
});
});
0x03 -
Sessions
DASHBOARD
ACCOUNT
HOME
ACCOUNT
HOME
DASHBOARD
LOGIN
What is a Session?
Browser
Website
Login
Unique ID
Hey, browser! Remember this Unique ID and for the rest of the times send me back this so I know who you are!
Cookies?
Browser
Server
Authenticate
Cookies
Cookies
HTTP Requests & Responses
{
"User-Agent": "Mozilla/5.0 ...",
"Accept": "text/html"
"Connection": "...",
"Keep-Alive": ""
}
<html>
...
</html>
Headers
Body
Creating Cookies
{
"Set-Cookie": "session=12345"
}
Body
Response
Creating multiple Cookies
{
"Set-Cookie": "alef=b; b=jim;"
}
Body
Reading Cookies
{
"Accept": text/html,
"Accept-Language": en-US,
"Cookie": "session=12345"
}
Body
client-sessions package
$ npm install client-sessions
const sessions = require("client-sessions");
app.use(sessions({
cookieName: "myOwnSession",
secret: "PrivateLongString",
duration: 30 * 60 * 1000, // 30 minutes
}));
const sessions = require("client-sessions");
app.use(sessions({
cookieName: "anotherSessionForMe",
secret: "PrivateLongString",
duration: 30 * 60 * 1000, // 30 minutes
}));
client-sessions package
Using Sessions
app.post("/login", (req, res) => {
User.findOne({ email: req.body.email }, (err, user) => {
if (err || !user || req.body.password !== user.password) {
return res.render("login", {
error: "Incorrect email / password."
});
}
req.session.userId = user._id;
res.redirect("/dashboard");
});
});
Demo
Dashboard page!
app.get("/dashboard", (req, res, next) => {
if (!(req.session && req.session.userId)) {
return res.redirect("/login");
}
User.findById(req.session.userId, (err, user) => {
if (err) {
return next(err);
}
if (!user) {
return res.redirect("/login");
}
res.render("dashboard");
});
});
0x04 -
Storing
Passwords
> db.users.find()
{
"_id" : ObjectId("5f21f14131eba444d3f7fb60"),
"firstName" : "Parsa",
"LastName" : "Hejabi",
"email" : "parsahejabi@parsahejabi.com",
"password" : "longlongstring",
"__v" : 0
}

Password hashing!
Hash
function
Password
LongRandomStringlkjalsdkflhaf
Password Hashing
- The same password always generates the same hash.
(Forget salting for now...) - Given a hash, you can never retrieve the original password that created it. Hashes are one-way functions!
Password hashing algorithms
- md5
- sha256
- bcrypt
- scrypt
- argon2
- ...
Password hashing algorithms
- md5
- sha256
- bcrypt
- scrypt
- argon2
- ...
- SUCKS!
- NO!
- OK
- AWESOME
- 5 YEARS
- NOPE!
Test bcrypt
Improving Registration
$ npm install bcryptjs
app.post("/register", (req, res) => {
let hash = bcrypt.hashSync(req.body.password, 14);
req.body.pashword = hash;
let user = new User(req.body);
user.save((err) => {
// ...
});
});
Improving Registration
app.post("/register", (req, res) => {
let hash = bcrypt.hashSync(req.body.password, 14);
req.body.pashword = hash;
let user = new User(req.body);
user.save((err) => {
// ...
});
});
Improving Login
app.post("/login", (req, res) => {
User.findOne({ email: req.body.email }, (err, user) => {
if (!user || !bcrypt.compareSync(req.body.password, user.password)) {
return res.render("login", {
error: "Incorrect email / password."
});
}
req.session.userId = user._id;
res.redirect("/dashboard");
});
});
Improving Login
app.post("/login", (req, res) => {
User.findOne({ email: req.body.email }, (err, user) => {
if (!user || !bcrypt.compareSync(req.body.password, user.password)) {
return res.render("login", {
error: "Incorrect email / password."
});
}
req.session.userId = user._id;
res.redirect("/dashboard");
});
});
Check the Database
> db.users.find()
{
"_id" : ObjectId("5f21f14131eba444d3f7fb60"),
"firstName" : "Parsa",
"LastName" : "Hejabi",
"email" : "parsahejabi@parsahejabi.com",
"password" : "$2a$14$t5v8pnwlYu2ncSbQWXJ1WORV0n.HQfZ.HccYXFEzmSf.spaRWlv0y",
"__v" : 0
}
Check the Database
> db.users.find()
{
"_id" : ObjectId("5f21f14131eba444d3f7fb60"),
"firstName" : "Parsa",
"LastName" : "Hejabi",
"email" : "parsahejabi@parsahejabi.com",
"password" : "$2a$14$t5v8pnwlYu2ncSbQWXJ1WORV0n.HQfZ.HccYXFEzmSf.spaRWlv0y",
"__v" : 0
}
Having fun!!! 🤦🏻♂️
0x05 -
Refactor
Smart User Middleware
app.use((req, res, next) => {
if (!(req.session && req.session.userId)) {
return next();
}
User.findById(req.session.userId, (err, user) => {
if (err) {
return next(err);
}
if (!user) {
return next();
}
user.password = undefined;
req.uesr = user;
res.locals.user = user;
next();
})
})
Smart User Middleware
app.use((req, res, next) => {
if (!(req.session && req.session.userId)) {
return next();
}
User.findById(req.session.userId, (err, user) => {
if (err) {
return next(err);
}
if (!user) {
return next();
}
user.password = undefined;
req.uesr = user;
res.locals.user = user;
next();
})
})
Smart User Middleware
app.use((req, res, next) => {
if (!(req.session && req.session.userId)) {
return next();
}
User.findById(req.session.userId, (err, user) => {
if (err) {
return next(err);
}
if (!user) {
return next();
}
user.password = undefined;
req.uesr = user;
res.locals.user = user;
next();
})
})
Force Authentication (Authorization :))
function loginRequired(req, res, next) {
if (!req.user) {
return res.redirect("/login");
}
next();
}
app.get("/dashboard", loginRequired, (req, res, next) => {
//...
});
Before
After
function loginRequired(req, res, next) {
if (!req.user) {
return res.redirect("/login");
}
next();
}
app.get("/dashboard", loginRequired, (req, res, next) => {
//...
});
app.get("/dashboard", (req, res, next) => {
if (!(req.session && req.session.userId)) {
return res.redirect("/login");
}
User.findById(req.session.userId, (err, user) => {
if (err) {
return next(err);
}
if (!user) {
return res.redirect("/login");
}
res.render("dashboard");
});
});
Before
After
function loginRequired(req, res, next) {
if (!req.user) {
return res.redirect("/login");
}
next();
}
app.get("/dashboard", loginRequired, (req, res, next) => {
//...
});
app.get("/dashboard", (req, res, next) => {
if (!(req.session && req.session.userId)) {
return res.redirect("/login");
}
User.findById(req.session.userId, (err, user) => {
if (err) {
return next(err);
}
if (!user) {
return res.redirect("/login");
}
res.render("dashboard");
});
});

Before
After
0x06 -
CSRF
CSRF?! 🤔
Cross-Site Request Forgery
???
<!-- bank.com/bardasht -->
<form>
<input type="text" name="account"/>
<input type="text" name="amount"/>
<input type="text" name="for"/>
</form>
CSRF
😈
New Email!
Yo Parsa,
Check out my new طوطی.
He can do the روپایی thing.
<a href="http://bank.com/bardasht?account=Parsa&amount=100000000&for=Dozd">
<img src=tooti.png>
</a>

CSRF Tokens
Browser
Server
CSRF Cookie
CSRF Tokens
Browser
Server
CSRF Cookie
GET /login
CSRF Tokens
Browser
Server
CSRF Cookie
GET /login
CSRF Tokens
Browser
Server
CSRF Cookie
GET /login
Login Page with CSRF Token
CSRF Tokens
Browser
Server
CSRF Cookie
GET /login
Login Page with CSRF Token
POST /login with
CSRF Token
CSRF Tokens
Browser
Server
CSRF Cookie
GET /login
Login Page with CSRF Token
POST /login with
CSRF Token
they are NOT the same
NO!
HTML(pug) solution!
form(method="post")
input(type="hidden", name="_csrf", value=csrfToken)
CSRF Protection
$ npm install csurf
const csurf = require("csurf");
app.use(csurf());
app.get("/register", (req, res) => {
res.render("register", { csrfToken: req.csrfToken() });
});
app.get("/login", (req, res) => {
res.render("login", { csrfToken: req.csrfToken() });
})
// Every place you have a form and call render!
CSRF Protection
const csurf = require("csurf");
app.use(csurf());
app.get("/register", (req, res) => {
res.render("register", { csrfToken: req.csrfToken() });
});
app.get("/login", (req, res) => {
res.render("login", { csrfToken: req.csrfToken() });
})
// Every place you have a form and call render!
0x07 -
Best
Practices
SSL! 🔐
Browser
Server
Secret
Cookie Flags
const sessions = require("client-sessions");
app.use(sessions({
cookieName: "myOwnSession",
secret: "PrivateLongString",
duration: 30 * 60 * 1000, // 30 minutes
}));
Cookie Flags
app.use(sessions({
cookieName: "myOwnSession",
secret: "PrivateLongString",
duration: 30 * 60 * 1000, // 30 minutes
activeDuration: 5 * 60 * 1000,
httpOnly: true, // JS code does not access cookies
secure: true, // only over https
ephermal: true // destroy cookies when the browser closes
}));
Use Helmet
$ npm install helmet

// Manages http header security for your application
const helmet = require("helmet");
app.use(helmet());
Other useful packages
Use them! 👍
- Passport
- Aqua
- Okta
- Node-login
Thank you! 👋
Feel free to contact me on Twitter or my website!


Authentication
By Parsa Hejabi
Authentication
These slides were used for a 1-hour meeting about authentication on 30th July 2020.
- 705