Slide 1

Slide 1 text

Everything You Ever Wanted to Know About Web Authentication in Node @rdegges *Almost

Slide 2

Slide 2 text

I’m Randall Degges Run Developer Advocacy at Okta Python / Node / Go Hacker

Slide 3

Slide 3 text

The Journey

Slide 4

Slide 4 text

REGISTER LOGIN DASHBOARD

Slide 5

Slide 5 text

0x00 - Getting Set Up

Slide 6

Slide 6 text

Assumptions Node https://nodejs.org/en/ Mongo https://www.mongodb.com/

Slide 7

Slide 7 text

Install Dependencies $ npm install express $ npm install pug

Slide 8

Slide 8 text

Prepare the App $ 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

Slide 9

Slide 9 text

Create Basic Templates ➔ Home ➔ Register ➔ Login ➔ Dashboard

Slide 10

Slide 10 text

block vars doctype html html head title SS-Auth | #{title} body block body SS-Auth | ➔ views/base.pug

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

extends base block vars - var title = "Dashboard" block body h1 Dashboard p. Welcome to your dashboard! You are now logged in. ➔ views/dashboard.pug

Slide 15

Slide 15 text

Create Basic Web App ➔ Define Routes ➔ Render Templates ➔ Start Server

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

$ node server.js Create Basic Templates

Slide 18

Slide 18 text

0x01 - Forms

Slide 19

Slide 19 text

First Name: Last Name: Email: Password: You know... HTML!

Slide 20

Slide 20 text

$ 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); }); Form Data

Slide 21

Slide 21 text

0x02 - Databases

Slide 22

Slide 22 text

$ mongo MongoDB shell version: 3.2.11 > Mongo

Slide 23

Slide 23 text

> use test; switched to db test > show collections; > db.users.insert({ email: "[email protected]", password: "woot!" }); WriteResult({ "nInserted" : 1 }) > db.users.find(); { "_id" : ObjectId("59247d0f3dacfc7ef19a41d2"), "email" : "[email protected]", "password" : "woot!" } > The Basics

Slide 24

Slide 24 text

$ npm install mongoose // server.js const mongoose = require("mongoose"); mongoose.connect("mongodb://localhost/ss-auth"); Mongoose (ORM)

Slide 25

Slide 25 text

// server.js const mongoose = require("mongoose"); 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 }, })); Models

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

> db.users.find() { "_id" : ObjectId("59248139c6670c68737612bd"), "firstName" : "Randall", "lastName" : "Degges", "email" : "[email protected]", "password" : "woot!", "__v" : 0 } Verifying

Slide 28

Slide 28 text

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"); }); }); Authenticating Users

Slide 29

Slide 29 text

0x03 - Sessions

Slide 30

Slide 30 text

Home Page Dashboard What If... Billing You had to enter your credentials over and over again on every page you visited?

Slide 31

Slide 31 text

Website The Idea Login Unique ID Hey! Remember this value! I need you to send it back to me next time you visit so I know who you are! Browser These are sessions.

Slide 32

Slide 32 text

Browser Server Cookies Cookies Authentication Cookies

Slide 33

Slide 33 text

HTTP Requests / Responses { "User-Agent": "Mozilla/5.0 ...", "Content-Type": "text/html" } … Headers Body

Slide 34

Slide 34 text

Set-Cookie: session=12345 Body { "Set-Cookie": "session=12345" } Creating Cookies Set-Cookie: a=b; c=d; e=f

Slide 35

Slide 35 text

Body { "User-Agent": "cURL/1.2.3", "Accept": "*/*", "Host": "localhost:3000", "Cookie": "session=12345" } Reading Cookies

Slide 36

Slide 36 text

client-sessions $ npm install client-sessions const sessions = require("client-sessions"); app.use(sessions({ cookieName: "session", secret: "woosafd32532wfsf", duration: 30 * 60 * 1000, // 30 mins }));

Slide 37

Slide 37 text

Using Sessions app.post("/login", (req, res) => { User.findOne({ email: req.body.email }, (err, user) => { if (!user || req.body.password !== user.password) { return res.render("login", { error: "Incorrect email / password." }); } req.session.userId = user._id; res.redirect("/dashboard"); }); });

Slide 38

Slide 38 text

Verifying

Slide 39

Slide 39 text

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"); }); }); Improving the Dashboard

Slide 40

Slide 40 text

0x04 - Storing Passwords

Slide 41

Slide 41 text

{ "_id" : ObjectId("59248139c6670c68737612bd"), "firstName" : "Randall", "lastName" : "Degges", "email" : "[email protected]", "password" : "woot!", "__v" : 0 } Current User Data

Slide 42

Slide 42 text

Password Hashing! Given a hash, you can never retrieve the original password that created it. Hashes are one-way. The same password always generates the same hash.

Slide 43

Slide 43 text

Hashing ● md5 ● sha256 ● bcrypt ● scrypt ● argon2 ● etc. ● SHITTY ● NOPE! ● SAFE ● AWESOME ● YES!! ● NO

Slide 44

Slide 44 text

bcrypt (pseudo) var password = 'hi'; var hash = bcrypt(password); console.log(hash); // $2a$10$uS.pE0aS0NlsgbvLd6EruO5VDKllinIZLF3C84OYzWHFiyKYfZVXy

Slide 45

Slide 45 text

Improving Registration $ npm install bcryptjs app.post("/register", (req, res) => { let hash = bcrypt.hashSync(req.body.password, 14); req.body.password = hash; let user = new User(req.body); user.save((err) => { // ...

Slide 46

Slide 46 text

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"); }); });

Slide 47

Slide 47 text

Our Improved User { "_id" : ObjectId("5924ad584f7ddf1eea467408"), "firstName" : "Randall", "lastName" : "Degges", "email" : "[email protected]", "password" : "$2a$14$czZ5IJCRsi7TvqwW8InVsOOnzBffT3e19unr1ecFq0XQGiTatN0wm", "__v" : 0 }

Slide 48

Slide 48 text

Homework! http://plaintextoffenders.com/

Slide 49

Slide 49 text

0x05 - Middleware

Slide 50

Slide 50 text

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(); } Smart User Middleware user.password = undefined; req.user = user; res.locals.user = user; next(); }); });

Slide 51

Slide 51 text

function loginRequired(req, res, next) { if (!req.user) { return res.redirect("/login"); } next(); } app.get("/dashboard", loginRequired, (req, res, next) => { Force Authentication

Slide 52

Slide 52 text

0x06 - CSRF

Slide 53

Slide 53 text

Let’s Say...

Slide 54

Slide 54 text

Cross Site Request Forgery Hey Randall, Check out this picture of my dog!

Slide 55

Slide 55 text

CSRF Tokens Browser Server CSRF Cookie GET /login Login Page w/ CT POST /login w/ CT NO! >:S

Slide 56

Slide 56 text

CSRF Protection in HTML form(method="post") input(type="hidden", name="_csrf", value=csrfToken)

Slide 57

Slide 57 text

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() }); }); // and all other places that call render // and have a form

Slide 58

Slide 58 text

0x07 - Security Best Practices

Slide 59

Slide 59 text

User Server secret Always Use SSL

Slide 60

Slide 60 text

Use Cookie Flags app.use(session({ cookieName: 'session', secret: 'some_random_string', duration: 30 * 60 * 1000, activeDuration: 5 * 60 * 1000, httpOnly: true, // don't let JS code access cookies secure: true, // only set cookies over https ephemeral: true // destroy cookies when the browser closes }));

Slide 61

Slide 61 text

Wear a Helmet const helmet = require("helmet"); // manage http header security app.use(helmet()); $ npm install helmet

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

Don’t Roll Your Own ➔ Passport ➔ Node-Login ➔ Aqua ➔ Okta

Slide 64

Slide 64 text

Code https://github.com/rdegges/ss-auth Slides https://speakerdeck.com/rdegges Resources @oktadev @rdegges

Slide 65

Slide 65 text

BONUS! JWTs suck for web authentication.

Slide 66

Slide 66 text

What are JWTs? - JSON data - Cryptographically signed - Not fancy - Not encrypted

Slide 67

Slide 67 text

What’s a Cryptographic Signature? Randall Degges That’s a signature!

Slide 68

Slide 68 text

What Do JWTs Actually Do? Prove that some data was generated by someone else.

Slide 69

Slide 69 text

How Do People Use JWTs?

Slide 70

Slide 70 text

Why Do JWTs Suck?

Slide 71

Slide 71 text

You’re Going to Hit the DB Anyway

Slide 72

Slide 72 text

Don’t Sign Things Twice Randall Degges That’s dumb! Randall Degges

Slide 73

Slide 73 text

Why ARE JWTs Good For? AUTH PROVIDER (GOOGLE) STATIC WEBSITE API