Parsa Hejabi
Special Thanks to Randall Degges
@rdegges
0x01
0x02
REGISTER
LOGIN
DASHBOARD
$ npm init -y
$ npm install express
$ npm install pug
$ 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
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
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
<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>
<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>
$ 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"
}
$ mongo
MongoDB shell version v4.2.0
> 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" }
> 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" }
$ npm install mongoose
// server.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true, useUnifiedTopology: true});
// 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"
);
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");
});
});
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"
}
> db.users.find()
{
"_id" : ObjectId("5f21f14131eba444d3f7fb60"),
"firstName" : "Parsa",
"LastName" : "Hejabi",
"email" : "parsahejabi@parsahejabi.com",
"password" : "longlongstring",
"__v" : 0
}
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");
});
});
DASHBOARD
ACCOUNT
HOME
ACCOUNT
HOME
DASHBOARD
LOGIN
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!
Browser
Server
Authenticate
Cookies
Cookies
{
"User-Agent": "Mozilla/5.0 ...",
"Accept": "text/html"
"Connection": "...",
"Keep-Alive": ""
}
<html>
...
</html>
Headers
Body
{
"Set-Cookie": "session=12345"
}
Body
Response
{
"Set-Cookie": "alef=b; b=jim;"
}
Body
{
"Accept": text/html,
"Accept-Language": en-US,
"Cookie": "session=12345"
}
Body
$ 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
}));
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");
});
});
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");
});
});
> db.users.find()
{
"_id" : ObjectId("5f21f14131eba444d3f7fb60"),
"firstName" : "Parsa",
"LastName" : "Hejabi",
"email" : "parsahejabi@parsahejabi.com",
"password" : "longlongstring",
"__v" : 0
}
Hash
function
Password
LongRandomStringlkjalsdkflhaf
$ 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) => {
// ...
});
});
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) => {
// ...
});
});
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");
});
});
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");
});
});
> db.users.find()
{
"_id" : ObjectId("5f21f14131eba444d3f7fb60"),
"firstName" : "Parsa",
"LastName" : "Hejabi",
"email" : "parsahejabi@parsahejabi.com",
"password" : "$2a$14$t5v8pnwlYu2ncSbQWXJ1WORV0n.HQfZ.HccYXFEzmSf.spaRWlv0y",
"__v" : 0
}
> db.users.find()
{
"_id" : ObjectId("5f21f14131eba444d3f7fb60"),
"firstName" : "Parsa",
"LastName" : "Hejabi",
"email" : "parsahejabi@parsahejabi.com",
"password" : "$2a$14$t5v8pnwlYu2ncSbQWXJ1WORV0n.HQfZ.HccYXFEzmSf.spaRWlv0y",
"__v" : 0
}
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();
})
})
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();
})
})
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();
})
})
function loginRequired(req, res, next) {
if (!req.user) {
return res.redirect("/login");
}
next();
}
app.get("/dashboard", loginRequired, (req, res, next) => {
//...
});
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");
});
});
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
???
<!-- bank.com/bardasht -->
<form>
<input type="text" name="account"/>
<input type="text" name="amount"/>
<input type="text" name="for"/>
</form>
😈
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>
Browser
Server
CSRF Cookie
Browser
Server
CSRF Cookie
GET /login
Browser
Server
CSRF Cookie
GET /login
Browser
Server
CSRF Cookie
GET /login
Login Page with CSRF Token
Browser
Server
CSRF Cookie
GET /login
Login Page with CSRF Token
POST /login with
CSRF Token
Browser
Server
CSRF Cookie
GET /login
Login Page with CSRF Token
POST /login with
CSRF Token
they are NOT the same
NO!
form(method="post")
input(type="hidden", name="_csrf", value=csrfToken)
$ 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!
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!
Browser
Server
Secret
const sessions = require("client-sessions");
app.use(sessions({
cookieName: "myOwnSession",
secret: "PrivateLongString",
duration: 30 * 60 * 1000, // 30 minutes
}));
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
}));
$ npm install helmet
// Manages http header security for your application
const helmet = require("helmet");
app.use(helmet());
Feel free to contact me on Twitter or my website!