Authentication

CSCI-UA.0480-008

Authentication vs Authorization

In the context of the web, what is authentication? Is it the same as authorization?

  • Authentication the process of determining whether or not who they claim to be.
  • Authorization is the set of rules that determine whether a user is allowed to perform an action that they are trying to perform.
  • You may hear authentication and authorization referred to as AuthN and AuthZ respectively
  • In today's class, we'll be discussing authentication

Authentication and the Web

How do websites verify that a user is who they claim to be? How do websites implement authentication?

  • traditionally through username and password
  • …what are some other ways?
    • requiring more than just a username and password (maybe both something you know and something you have) … any examples of this two-factor auth thing?
    • (a code that's texted to your phone - think gmail's 2-factor auth, or a dedicated device, like yubikey)

    • some sort of integration with with a social media site that can vouch for your credentials!
      • like Facebook Connect
      • or Google Sign-In

Before We Go On

If our site collects any sensitive information from a user, the communication between our server and the client should be encrypted. To do this, you'll need to use TLS/SSL (that's when you see the padlock icon and https in the schema part of the url):

TLS/SSL are cryptographic protocols

  1. a method to encrypt traffic between the server and client
  2. a typical exchange involves the following steps:
    • the client and server agree on which protocol and version to use
    • the server sends back a cryptographically signed certificate supplied from a trusted third party
    • this certificate is used by the browser to verify the identity of the server that the browser is connecting to
    • keys for encryption (a parameter to an encryption algorithm) are exchanged, and are subsequently used to communicate through symmetric encryption

Where do I Get My Cert?

You'll have to: →

  • buy a cert from a Certificate Issuer, like digicert, geotrust, etc.
  • or use a cert from Let's Encrypt.


Let's Encrypt is a free certificate authority backed by a non-profit. Check out:

Using SSL/TLS

A fully detailed lecture on security and encryption is beyond the scope of this class (we'll talk a little more about tls/ssl). However, you should know there's support for TLS/SSL in Node.js and Express.

  • you'll have to obtain a certificate from a certificate issuer, such as DigiCert, Comodo, Let's Encrypt etc.
  • (you can also create a self-signed one (not suitable for production)
  • …and configure express to use it (notice that you have to explicitly call createServer, and that the options may vary depending on how you've obtained your cert - see relevant docs)

// require http, https, express, etc.
const options = {
	key: fs.readFileSync(__dirname + '/ssl/server.pem'), 
	cert: fs.readFileSync(__dirname + '/ssl/server.crt'),
};
https.createServer(options, app).listen(app.get('port'), function(){ 
	console.log('Express started ...');
});

Use TLS/SSL!

If you're curious about how it works under the hood:

  1. we'll cover a little bit more about tls/ssl in the next set of slides
  2. check out this StackExchange Information Security article on TLS/SSL


Also, we can actually check out certs in our browser.

(try going to home.nyu.edu in chrome… and check on the padlock)

Back to Authentication

Ok… now that that's out of the way… If we'd like to add username and password for authentication, where do we store that information?

Our database makes sense, of course, but what would our Schema look like, and what would the contents be of each field?

Simple enough… just two fields, username to store username and password to store password. Easy!


const userSchema = mongoose.Schema({
{
	username: String,
	password: String,
})

The Password Field

That password field is just the password in plain text. Why is storing a password in plain text a bad idea?

The data in our database may be compromised (how? →):

  • someone hacking into the database server and obtaining users' passwords
  • an inadvertent leak of data from a misconfigured server
  • a person that has access to the database server misusing their access privileges to read sensitive information
  • database backups being lost or stolen
  • (yikes! …more on this)

Don't Store Passwords in Plain Text

Hashing vs Encryption

Both are ways that we can use to transform a string into another string… but what's the difference between the two?

  • hashing is a one way function (mapping)
  • encryption is a two way function
    • it's reversible
    • you can decrypt an encrypted string


Which do you think is appropriate for storing passwords? Why?

  • we should hash our passwords
  • if the transformation were reversible, then it would be possible to retrieve the actual passwords!

Hashing Passwords

Ok… so, how do I find or create an adequate hashing algorithm? What are some properties that we would look for?

  • kind of a trick question
  • but, maybe some characteristics are:
    • collision resistant
    • computationally difficult to generate (why? … we'll see in the next couple of slides →)

Hashing Algorithms

It turns out that these are the ones that are recommended:

  1. bcrypt
  2. PBKDF2


But only for now … as the landscape continues to change:

  • computational power increases
  • flaws found in existing algorithms
  • better algorithms discovered

Common Attacks Against Hashed Passwords

What are some ways of figuring out a password from a hash? (You'll see why the hashing algorithm should be computationally expensive)

  • most naive way is to guess the password, hash the guess… and compare with the password's hash using:
    • brute force - construct every possible string up to a length and use that as a guess
    • dictionary - use a set of known passwords (super easy to find)
  • precompute hashes and use a lookup table
  • and others

Are We Done Yet?

Is a one way hash of a password adequate? Are we done yet? What's another consideration?

What can be inferred from two passwords if their hash is the same?

They're the same passwords! If you figure out one, you've figured out the other.

How can we make the hash of two of the same passwords different from eachother?

Add salt.

Salting and Hashing

To prevent the hash of two of the same passwords from being the same, we can salt the password.

  • add a random string (you'll need to store the salt as well as the password in your database) to the password
  • …hash the string formed from the salt and password
  • the salt should be unique per-user, per-password (don't reuse salts… why? →)
    • two users with the same password will still have the same hash!

TLS/SSL and Storing Passwords Summary

  • use TLS/SSL to encrypt traffic between server and client
  • never store passwords in plain text
  • hash passwords (one way, unlike encryption, which is reversible)
  • salt and hash passwords


And here are some particularly good resources

Back to Authentication

Assuming that we have all of the previous stuff on password storage right. What's next? We'll need to manage:

  • a way to authenticate a user
  • keep that authentication persistent through a user's session

Passport

We'll use the following node modules for authentication and session management:

Passport Usage

So… what does it actually do?

  • handles authentication (ask for user and password)
  • persists that authentication (session management)
  • supplies a req.user object
    • enabled when user is authenticated
    • you can use to access username, determine if authenticated, etc.


To setup passport… you'll need to

  1. Specify authentication strategies (how we want someone to be able to login)
  2. Enable the middleware
  3. Enable sessions

Passport Strategies

Passport uses strategies to authenticate a request. There are multiple ways to authenticate a user (we mentioned them before). What are some possible authentication strategies?

  • check for username and password in the database (called local strategy)
  • Facebook Connect
  • Google
  • authentication protocols, such as OAuth, OpenID


We'll be using local authentication… authentication with a username and password stored in a local database (MongoDB).

Passport Strategies Continued

When we create a strategy, we define a callback function that:

  1. finds and returns the user that possesses a set of credentials
  2. for our local strategy, that means we have to retrieve a user from our database using their username and (hashed) password
  3. soooo… we could write this function ourselves, or use a module that does this for us (we'll take the easier route: use passport-local-mongoose)

Middleware

We also have to activate two pieces of middleware:

  • passport.initialize - to start up passport
  • passport.session - to enable persistent login sessions

app.use(passport.initialize());
app.use(passport.session());

Sessions

Username and password (credentials) are usually only transmitted once during the initial login request.

  • once a user is authenticated…
  • a session is created and maintained…
  • via data stored on the server that's associated with a cookie in the user's browser
  • (each subsequent request will not have the username and password, but instead, the cookie that identifies the session)

Sessions Continued

To support login sessions, Passport will serialize and deserialize instances of the user object to and from the session store (for us the session store is in memory).

By the way, what do we mean by serialization?

Translate a data structure / object to a storable format). We'll have to define functions that do this and tell passport all about it or rely on (again) a module.


passport.serializeUser(function(user, done) {
	done(null, user.id);
});

passport.deserializeUser(function(id, done) {
	User.findById(id, function(err, user) {
		done(err, user);
	});
});

TL;DR Passport

Passport is middleware that authenticates requests. It'll give us:

  • a req.user object that contains the currently logged in user
  • use() - to specify an authentication strategy (how we want users to be able to login)
  • serializeUser() and deserializeUser() - specifies how to store / retrieve a user from the session (and populate req.user)
  • authenticate() - to authenticate a request using a specified strategy (there are various ways to use this…. as middleware or as a plain function called within your route handler)
  • req.login() - to start a logged in session (once a user has been authenticated)

Steps for Site Registration and Login

If someone registers for our site, what are the steps that we should take for storing their login/password info?

  1. generate a salt
  2. append or prepend that salt to the supplied password
  3. hash the resulting string (some algorithms suggest multiple passes)
  4. save both the salt and the hash


And what about logging in… how can we tell if a person's password is correct. What steps should we take?

  1. look up the username
  2. retrieve the password hash
  3. salt and hash the incoming password
  4. compare hashes

Passport Local Mongoose

Again, Passport allows the flexibility of writing our own strategy for:

  • registration
  • storing a password
  • login
  • checking username / password.


However… that's a lot of work, and it's easy to get that stuff wrong (for something so important, it's maybe too easy to get wrong).

Passport-Local Mongoose is a plugin for Mongoose that bundles up all of that functionality by bringing together passport and Mongoose.

What Does Passport Local Mongoose Do?

It provides a bunch of static methods for us - that we otherwise have to write on our own - for:

  • register() - actually create a new user (with salting and hashing, of course)
  • authenticate() - local strategy authentication (checking for username / passwoord)
  • serializeUser() - for storing a user in the session
  • deserializeUser() - for retrieving a user in the session (and populating req.user)


We have an idea what these might look like, right? Let's check the actual implementation

Phew! Ok… let's get into some interaction design stuff for a moment…

Login / Registration Considerations

  • what are some possible outcomes of login that we'll have to handle?
    • user does not exist
    • password incorrect
    • how specific should the error message be? →
    • specificity helps usability, but may be a vector for snooping for valid usernames
  • …and registration errors
    • user already exists
    • password or username doesn't meet requirements

Considerations Continued

Outside of registration and login, what else might we need to support if we have username/password authentication?

  • password reset? how?
    • … common practice is to send email with reset link
    • … obvs, not send new password!
  • forgot username?

Implementation (Uh-oh, live demo time)

Demo Overview

We'll support the following features:

  • users that have usernames and passwords
  • login
  • register

User Schema

As usual, we'll create a db.js that contains our schemas, registers our models and connects to the database.

The user schema can be totally blank. Passport local mongoose will add properties to the schema, as well as some static methods!

  • username, password, salt
  • authenticate, serialize, deserialize, etc.

const mongoose = require('mongoose');
const passportLocalMongoose = require('passport-local-mongoose');

const UserSchema = new mongoose.Schema({ });

UserSchema.plugin(passportLocalMongoose);

mongoose.model('User', UserSchema);
mongoose.connect('mongodb://localhost/class16db');

Some Passport Setup

In a file called auth.js in the root of your project, let passport know what strategy you want to use as well as how to serialize and deserialize a user:


const mongoose = require('mongoose'),
	passport = require('passport'),
	LocalStrategy = require('passport-local').Strategy,
	User = mongoose.model('User');

// hey... one of those static functions that passport-local
// mongoose gives our model...
passport.use(new LocalStrategy(User.authenticate()));

passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

Prepping app.js

At the top of app.js, bring in the two files that we created, db.js and auth.js:


require('./db');
require('./auth');

const passport = require('passport');

Add Session Support

We've done this before (put this after you've created your app object):


const session = require('express-session');
const sessionOptions = {
	secret: 'secret cookie thang (store this elsewhere!)',
	resave: true,
	saveUninitialized: true
};
app.use(session(sessionOptions));

Enable the Passport Middleware

Start up passport and allow login sessions using the following middleware (do this before defining/using your routes!):


app.use(passport.initialize());
app.use(passport.session());

Make User Data Available to All Templates

Add some middleware that drops req.user into the context of every template. This is done by adding properties to res.locals.


app.use(function(req, res, next){
	res.locals.user = req.user;
	next();
});

Wowz. Lots of Setup, and we Don't Even Have Routes!

Requires for our Routes

In a file in the routes directory, let's setup our usual set of requires for creating routers. Additionally, add dependencies for passport and mongoose so that we can actually login and register. (Don't forget to export your router too)


const express = require('express'), 
    router = express.Router(),
    passport = require('passport'),
    mongoose = require('mongoose'),
    User = mongoose.model('User');

// route handlers go above
module.exports = router;

The Easy Ones (Home, Login and Reg Forms)

These route handlers will handle requests to home, the login form and the registration form:


router.get('/', function(req, res) {
  res.render('index');
});

router.get('/login', function(req, res) {
  res.render('login');
});

router.get('/register', function(req, res) {
  res.render('register');
});

Templates for Login and Register

The templates for both of these will pretty much be the same. The auth strategy we use expect username and password, so we'll name our input fields that.

We'll also reserve a spot for error messages.


...

username: password:

Lastly, it might be nice to drop in username in our layouts.hbs (Remember, we put user into the context using some not-so-fancy middleware).


{{#if user}}
Logged in as {{user.username}}
{{/if}}

Registration Post

Our registration post handler will create a new user or render an error if something goes wrong. If a new user is created, go ahead and log them in!


router.post('/register', function(req, res) {
  User.register(new User({username:req.body.username}), 
      req.body.password, function(err, user){
    if (err) {
      res.render('register',{message:'Your registration information is not valid'});
    } else {
      passport.authenticate('local')(req, res, function() {
        res.redirect('/');
      });
    }
  });   
});

Login Post

Ugh… so login is a bit weird. Here, we authenticate, and on successful authentication, use req.logIn to start the logged in session.

Otherwise, render an error message…


router.post('/login', function(req,res,next) {
  passport.authenticate('local', function(err,user) {
    if(user) {
      req.logIn(user, function(err) {
        res.redirect('/');
      });
    } else {
      res.render('login', {message:'Your login or password is incorrect.'});
    }
  })(req, res, next);
});

Canned Demos

There are a couple of demos that I've created in the examples repository (you need to be logged in to github to see these):

  • A bare minimum demo using passport-local-mongoose
  • Another version that has more user specific features


The 2nd version allows a user to store image urls.

  • outside of login and register, there's a single page: /users/some-username
  • … that shows all of the images urls that someone has saved
  • if the person is logged in, the same page will show a form to add another image

Check Out

  • defining (or supplying) the strategy
  • the actual passport-local-mongoose code
  • the schema that utilizes the passport-local-mongoose plugin
  • the middleware
  • the routes; watch out for…
    • using req.body
    • calls to authenticate
    • populating related schemas!

Other Tutorials / Demos