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

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

Want to learn how web authentication works? How your login information is transmitted from a web browser to a web server, and what happens from that point onwards? How authentication protocols work behind the scenes?

In this talk, Randall Degges, Developer Advocate at Okta, will walk you through the entire web authentication flow, covering:

- Credential transmission
- Cookies
- Sessions
- Databases
- Best practices

By the end of this talk, you’ll be intimately familiar with web authentication in Node.

56badf521701d4f9b3a394d3ef6e90c4?s=128

Randall Degges

June 21, 2017
Tweet

Transcript

  1. Everything You Ever Wanted to Know About Web Authentication in

    Node @rdegges *Almost
  2. I’m Randall Degges Run Developer Advocacy at Okta Python /

    Node / Go Hacker
  3. The Journey

  4. REGISTER LOGIN DASHBOARD

  5. 0x00 - Getting Set Up

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

  7. Install Dependencies $ npm install express $ npm install pug

  8. 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
  9. Create Basic Templates ➔ Home ➔ Register ➔ Login ➔

    Dashboard
  10. block vars doctype html html head title SS-Auth | #{title}

    body block body <!DOCTYPE html> <html> <head> <title>SS-Auth | </title> </head> <body> </body> </html> ➔ views/base.pug
  11. 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
  12. 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
  13. 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
  14. extends base block vars - var title = "Dashboard" block

    body h1 Dashboard p. Welcome to your dashboard! You are now logged in. ➔ views/dashboard.pug
  15. Create Basic Web App ➔ Define Routes ➔ Render Templates

    ➔ Start Server
  16. 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
  17. $ node server.js Create Basic Templates

  18. 0x01 - Forms

  19. <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> You know... HTML!
  20. $ 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
  21. 0x02 - Databases

  22. $ mongo MongoDB shell version: 3.2.11 > Mongo

  23. > use test; switched to db test > show collections;

    > db.users.insert({ email: "r@rdegges.com", password: "woot!" }); WriteResult({ "nInserted" : 1 }) > db.users.find(); { "_id" : ObjectId("59247d0f3dacfc7ef19a41d2"), "email" : "r@rdegges.com", "password" : "woot!" } > The Basics
  24. $ npm install mongoose // server.js const mongoose = require("mongoose");

    mongoose.connect("mongodb://localhost/ss-auth"); Mongoose (ORM)
  25. // 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
  26. 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
  27. > db.users.find() { "_id" : ObjectId("59248139c6670c68737612bd"), "firstName" : "Randall", "lastName"

    : "Degges", "email" : "r@rdegges.com", "password" : "woot!", "__v" : 0 } Verifying
  28. 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
  29. 0x03 - Sessions

  30. Home Page Dashboard What If... Billing You had to enter

    your credentials over and over again on every page you visited?
  31. 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.
  32. Browser Server Cookies Cookies Authentication Cookies

  33. HTTP Requests / Responses { "User-Agent": "Mozilla/5.0 ...", "Content-Type": "text/html"

    } <html> … </html> Headers Body
  34. Set-Cookie: session=12345 Body { "Set-Cookie": "session=12345" } Creating Cookies Set-Cookie:

    a=b; c=d; e=f
  35. Body { "User-Agent": "cURL/1.2.3", "Accept": "*/*", "Host": "localhost:3000", "Cookie": "session=12345"

    } Reading Cookies
  36. client-sessions $ npm install client-sessions const sessions = require("client-sessions"); app.use(sessions({

    cookieName: "session", secret: "woosafd32532wfsf", duration: 30 * 60 * 1000, // 30 mins }));
  37. 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"); }); });
  38. Verifying

  39. 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
  40. 0x04 - Storing Passwords

  41. { "_id" : ObjectId("59248139c6670c68737612bd"), "firstName" : "Randall", "lastName" : "Degges",

    "email" : "r@rdegges.com", "password" : "woot!", "__v" : 0 } Current User Data
  42. 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.
  43. Hashing • md5 • sha256 • bcrypt • scrypt •

    argon2 • etc. • SHITTY • NOPE! • SAFE • AWESOME • YES!! • NO
  44. bcrypt (pseudo) var password = 'hi'; var hash = bcrypt(password);

    console.log(hash); // $2a$10$uS.pE0aS0NlsgbvLd6EruO5VDKllinIZLF3C84OYzWHFiyKYfZVXy
  45. 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) => { // ...
  46. 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"); }); });
  47. Our Improved User { "_id" : ObjectId("5924ad584f7ddf1eea467408"), "firstName" : "Randall",

    "lastName" : "Degges", "email" : "r@rdegges.com", "password" : "$2a$14$czZ5IJCRsi7TvqwW8InVsOOnzBffT3e19unr1ecFq0XQGiTatN0wm", "__v" : 0 }
  48. Homework! http://plaintextoffenders.com/

  49. 0x05 - Middleware

  50. 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(); }); });
  51. function loginRequired(req, res, next) { if (!req.user) { return res.redirect("/login");

    } next(); } app.get("/dashboard", loginRequired, (req, res, next) => { Force Authentication
  52. 0x06 - CSRF

  53. Let’s Say... <!-- bank.com/withdraw --> <form> <input type="text" name="account"/> <input

    type="text" name="amount"/> <input type="text" name="for"/> </form>
  54. Cross Site Request Forgery Hey Randall, Check out this picture

    of my dog! <img src="http://bank.com/withdraw?account=Randall&amp ;amount=1000000&amp;for=BadGuy">
  55. CSRF Tokens Browser Server CSRF Cookie GET /login Login Page

    w/ CT POST /login w/ CT NO! >:S
  56. CSRF Protection in HTML form(method="post") input(type="hidden", name="_csrf", value=csrfToken)

  57. 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
  58. 0x07 - Security Best Practices

  59. User Server secret Always Use SSL

  60. 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 }));
  61. Wear a Helmet const helmet = require("helmet"); // manage http

    header security app.use(helmet()); $ npm install helmet
  62. None
  63. Don’t Roll Your Own ➔ Passport ➔ Node-Login ➔ Aqua

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

  65. BONUS! JWTs suck for web authentication.

  66. What are JWTs? - JSON data - Cryptographically signed -

    Not fancy - Not encrypted
  67. What’s a Cryptographic Signature? Randall Degges That’s a signature!

  68. What Do JWTs Actually Do? Prove that some data was

    generated by someone else.
  69. How Do People Use JWTs?

  70. Why Do JWTs Suck?

  71. You’re Going to Hit the DB Anyway

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

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

    API