Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Randall Degges

June 21, 2017
Tweet

More Decks by Randall Degges

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. extends base block vars - var title = "Dashboard" block

    body h1 Dashboard p. Welcome to your dashboard! You are now logged in. ➔ views/dashboard.pug
  7. 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
  8. <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!
  9. $ 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
  10. > 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
  11. $ npm install mongoose // server.js const mongoose = require("mongoose");

    mongoose.connect("mongodb://localhost/ss-auth"); Mongoose (ORM)
  12. // 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
  13. 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
  14. 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
  15. Home Page Dashboard What If... Billing You had to enter

    your credentials over and over again on every page you visited?
  16. 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.
  17. client-sessions $ npm install client-sessions const sessions = require("client-sessions"); app.use(sessions({

    cookieName: "session", secret: "woosafd32532wfsf", duration: 30 * 60 * 1000, // 30 mins }));
  18. 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"); }); });
  19. 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
  20. 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.
  21. Hashing • md5 • sha256 • bcrypt • scrypt •

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

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

    "lastName" : "Degges", "email" : "[email protected]", "password" : "$2a$14$czZ5IJCRsi7TvqwW8InVsOOnzBffT3e19unr1ecFq0XQGiTatN0wm", "__v" : 0 }
  26. 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(); }); });
  27. function loginRequired(req, res, next) { if (!req.user) { return res.redirect("/login");

    } next(); } app.get("/dashboard", loginRequired, (req, res, next) => { Force Authentication
  28. Let’s Say... <!-- bank.com/withdraw --> <form> <input type="text" name="account"/> <input

    type="text" name="amount"/> <input type="text" name="for"/> </form>
  29. 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">
  30. 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
  31. 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 }));
  32. Wear a Helmet const helmet = require("helmet"); // manage http

    header security app.use(helmet()); $ npm install helmet