- overview
- Folder Structure
- Setup
- Express App and Server
- The Router File
- The Model
- Mongoose Setup
- The Authentication Controller
- Creating Items in the DB
- Creating the Route Handler
- Bcrypt
- JSON Web Token(JWTs)
- Passport
- Authentication Routes
-
process of authentication:
- client makes request
- server looks at username/pw
- once credentionals are verified the server gives client an identifying piece of info
-
types of identifying pieces of info
- cookies:
- included in HTTP requests by default
- brings state into the HTTP protocol.
- Cannot be set to different domaints
- tokens:
- have to be wired up manually
- can be set to any domain
- controllers/authentication.js
- exports functions for signing in / signing up user when they go to the signin endpoint
- models/user.js
- creates and exports a user model/schema
- when creating the password contains
pre
middlewhere to encrypt the pw using bcrypt - contains the logic to compare passwords when the user logs in
- services/passport.js
- contains passport strategies
- a local strategy for when the first user logs on
- a jwt strategy for authenticating the user's token when they go to protected routes
- contains passport strategies
- config.js
- contains a secret - used for encrypting the JWT
- index.js
- creates and sets up the express app
- connects to the DB
- sets up the server
- router.js
- exports functions for when the user visits
/siginin
or/signup
- includes logic for protecting certin routes
- exports functions for when the user visits
- gitigore
- to prevent the secret and node modules from being commited to GH
$ npm int
to create a package.json folder- install dependencies
$ npm install --save express mongoose morgan body-parser
- create a
.gitingore
file and put node_modules here (secret for encrypting pw will eventually go here also)
- create the express app
- in
index.js
const express = require('express'); // library for handling http requests const app = express(); // creates an instance of express
- import the http module and set up the server
- in
index.js
const http = require('http'); const port = process.env.PORT || 3090; const server = http.createServer(app); server.listen(port); console.log('Server listening on', port);
- create the initial route for when the user reaches the home screen
- in
router.js
module.exports = function(app) { app.get('/', function(req, res) { res.send({ greeting: 'hi' }) }) }
- export the function and import it into the index.js file
- in
index.js
const router= require('./router'); router(app);
- test to see if the route works
- import the mongoose module and the Schema object
- in
models/user.js
const mongoose = require('mongoose'); const Schema = mongoose.Schema;
- create the Schema and make sure the email is unique
- in
models/user.js
const userSchema = new Schema({ email: { type: String, unique: true, lowercase: true }, password: String });
- create and export the module'
- in
models/user.js
const ModelClass = mongoose.model('user', userSchema); // user model = represents a class of users module.exports = ModelClass;
- import the mongoose module
- in
index.js
const mongoose = require('mongoose');
- connect to the mongoose server (if the table does not exists its created)
- in
index.js
mongoose.connect('mongodb://localhost/auth');
- export a function
- in
controllers/authentication.js
exports.signup = function(req, res, next) { res.send({ test: "signup" }) }
- wire up in route handler in router.js
- in
router.js
const Authentication = require('./controllers/authentication'); module.exports = function(app) { app.post('/signin', Authentication.signup); }
- inmport and wire up body parser in index.js; this is used for parsing requests in the body
- in
index.js
const bodyParser = require('body-parser'); app.use(bodyParser.json({ type: '*/*' }))
- import the User model into the controller file
- in
controllers/authentication.js
const User = require('../models/user');
- in the signup function - pull the email and password off of the request object
- in
controllers/authentication.js
const email = req.body.email; const password = req.body.password;
- make sure an email and password are provided, if not send a message
if (!email || !password) { return res.status(422).send({ error: 'You must provide an email and password'}); }
- see if the user provided exists, if it does return an error, if not - create and save the new user to the DB
User.findOne({ email: email }, function(err, existingUser) {
if (err) { return next(err) }
if (existingUser) {
res.status(422).send({ error: 'Email is in use'})
}
const user = new User({
email: email,
password: password
});
user.save(function(err) {
if (err) { return next(err); }
res.json({ test: 'user created' }); // JWT will eventually go here
});
})
- test out to see if user is actually saved
- once we have their username and pw auth'd (with passport), we give them their token
- in
controllers/authentication.js
exports.signin = function(req, res, next) {
res.send({ token: tokenForUser(req.user) })
}
- never save passwords as plaintext, store as an encryption (bcrypt)
- salt + submitted password = hashed password
- install bcrypt:
$ npm install --save bcrypt-nodejs
- wire up Bcrypt using
.pre
- in
models/user.js
userSchema.pre('save', function(next) { // encryption logic for the pw will go here }
- generate a salt and then run a function where the salted pw will be the second arg, also get access to the user model
userSchema.pre('save', function(next) { const user = this; // getting access to the user model; user = instance of the user model bcrypt.genSalt(10, function(err, salt) { // here we will assign the user given password to the salted password } })
- assign the user given password to the salted password
bcrypt.genSalt(10, function(err, salt) { bcrypt.hash(user.password, salt, null, function(err, hash) { if (err) { return next(err) } user.password = hash; next(); // moves on to save the model }) })
- now when user is saved, you should only see the encrypted password
- an identifying piece of information given to user for future requests
- after user is saved - send back JWT
- user id + secret string = JWT
- need to keep string 100% secret (keep in config file and put in .gitignore)
- to create a JSON web token:
- install jwt-simple
npm install --save jwt-simple
- create a config.js file and create a secret string
- in
config.js
secret: 'fadhjafskdlfhasjlkdfhure' // can be any random string }
- import config and jwt lib into auth.js
- in
controllers/authentication.js
const jwt = require('jwt-simple'); const config = require('../config');
- create a function that takes a user and returns a new token using the secret
- in
controllers/authentication.js
function tokenForUser(user) { const timestamp = new Date().getTime(); return jwt.encode({ sub: user.id, iat: timestamp }, config.secret); }
- properties within json token object:
sub
= subject, in our case the useriat
= issued at time, will be the timestamp
- in the signup function, have the JWT returned in the response after the user is successfully saved
exports.signup = function(req, res, next) { //... user.save(function(err) { res.json({ token: tokenForUser(user) }); }); }) }
- a library for authentication/ verifcation
- authenticates the user when they loggin
- verifies the user when they visit protected route
- install passport and passport-jwt
npm install --save passport passport-jwt
- create a passport.js file in a folder called services and import modules
- in
services/passport.js
const passport = require('passport'); const User = require('../models/user'); const config = require('../config'); const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; const LocalStrategy = require('passport-local');
- strategies - methods for authenticating a user. EX: can verify with JWT or username/password (local)
- set up options, by default expects a username so we need to explicitey state we are using an email
- in
services/passport.js
const localOptions = { usernameField: 'email' };
- need to add a method to the user model to do password comparison
- use the methods property to create a new method called Passwords
- in
models/user.js
userSchema.methods.comparePassword = function(candidatePassword, callback) { // this = reference to our user model // this.password = hashed and salted password bcrypt.compare(candidatePassword, this.password, function(err, isMatch) { if (err) { return callback(err); } // if passwords match - isMatch is true; else false callback(null, isMatch) }) }
- create the strategy. The strategy will take the email and password the user submitted and will search the db and then use the
comparepassords
method defined in the user model to compare the pw submitted to the pw in the db. If it finds a matching username/pw it will return the user, else it will return null. - in
services/authentication.js
const localLogin = new LocalStrategy(localOptions, function(email, password, done) { User.findOne({ email: email }, function(err, user) { if (err) { return done(err) } // err in search if (!user) return done(null, false) user.comparePassword(password, function(err, isMatch) { if (err) { return done(err); } if (!isMatch) { return done(null, false) } return done(null, user); }) }) });
- tell passport to use the strategy
passport.use(localLogin);
- in the callback function uses the JWT payload - a decoded JWT token. Includes a subject (user id) and timestamp. The strategy checks if the user in the payload (sub) is in the db. If it is it returns a user, if not it returns false.
- create the jwt options
- in
services/authentication.js
const jwtOptions = { // looks in the header called authorization for the token jwtFromRequest: ExtractJwt.fromHeader('authorization'), secretOrKey: config.secret };
- in
services/authentication.js
const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) { User.findById(payload.sub, function(err, user) { if (err) { return done(err, false)} if (user) { done(null, user); } else { done(null, false) } }) })
- tell passport to use the strategy:
passport.use(jwtLogin);
-
we need to tell particular routes to use passport, use auth object as middleware
app.get(<route>, requireAuth, () => {})
-
import modules:
-
in
router.js
const Authentication = require('./controllers/authentication'); const passportService = require('./services/passport'); const passport = require('passport');
-
create authentication with the JWT (JWT strategy)
- create an object to unsert between incoming requests and the route handler
const requireAuth = passport.authenticate('jwt', { session: false }); // session false = not cookie based
- use the objects to insert in between the route and the route handler
app.get('/', requireAuth, function(req, res) { res.send({ hi: 'there' }); }) app.post('/signup', Authentication.signup);
- create an object to unsert between incoming requests and the route handler
-
create authentication using the email/password (local strategy)
- create the requireSignin object that will be used as middleware
const requireSignin = passport.authenticate('local', { session: false });
- add the
requreSignin
object as middleware
app.post('/signin', requireSignin, Authentication.signin);
-
to test out:
/signup
: send a post with new user in body - should give back a response object as a token/
- use JWT response object in the header to go to protected route/signin
: send a post request with existing user - should get back JWT to insert in header to go to existing routes