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. Everything You Ever
    Wanted to Know
    About Web Authentication in Node
    @rdegges
    *Almost

    View Slide

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

    View Slide

  3. The Journey

    View Slide

  4. REGISTER LOGIN DASHBOARD

    View Slide

  5. 0x00 - Getting Set Up

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  9. Create Basic Templates
    ➔ Home
    ➔ Register
    ➔ Login
    ➔ Dashboard

    View Slide

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



    SS-Auth |




    ➔ views/base.pug

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  17. $ node server.js
    Create Basic Templates

    View Slide

  18. 0x01 - Forms

    View Slide


  19. First Name:
    Last Name:
    Email:
    Password:


    You know... HTML!

    View Slide

  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

    View Slide

  21. 0x02 - Databases

    View Slide

  22. $ mongo
    MongoDB shell version: 3.2.11
    >
    Mongo

    View Slide

  23. > 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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  29. 0x03 - Sessions

    View Slide

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

    View Slide

  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.

    View Slide

  32. Browser Server
    Cookies
    Cookies
    Authentication
    Cookies

    View Slide

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



    Headers
    Body

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  38. Verifying

    View Slide

  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

    View Slide

  40. 0x04 - Storing Passwords

    View Slide

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

    View Slide

  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.

    View Slide

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

    View Slide

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

    View Slide

  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) => {
    // ...

    View Slide

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

    View Slide

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

    View Slide

  48. Homework!
    http://plaintextoffenders.com/

    View Slide

  49. 0x05 - Middleware

    View Slide

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

    View Slide

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

    View Slide

  52. 0x06 - CSRF

    View Slide

  53. Let’s Say...






    View Slide

  54. Cross Site Request Forgery
    Hey Randall,
    Check out this picture of my dog!
    src="http://bank.com/withdraw?account=Randall&amp
    ;amount=1000000&for=BadGuy">

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  58. 0x07 - Security Best Practices

    View Slide

  59. User Server
    secret
    Always Use SSL

    View Slide

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

    View Slide

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

    View Slide

  62. View Slide

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

    View Slide

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

    View Slide

  65. BONUS!
    JWTs suck for web authentication.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  69. How Do People Use JWTs?

    View Slide

  70. Why Do JWTs Suck?

    View Slide

  71. You’re Going to Hit the DB Anyway

    View Slide

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

    View Slide

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

    View Slide