Skip to content

Instantly share code, notes, and snippets.

@nonlogos
Last active June 2, 2018 04:15
Show Gist options
  • Save nonlogos/5cb9ce7559cf6a63de84723076f34c28 to your computer and use it in GitHub Desktop.
Save nonlogos/5cb9ce7559cf6a63de84723076f34c28 to your computer and use it in GitHub Desktop.
basic API Server with Node, Express and Passport Authentication
// mkdir server
// dependencies
// npm install --save express mongoose morgan body-parser nodemon bcrypt-nodejs jwt-simple passport passport-jwt cors
- express
- mongoose
- morgan
- body-parse
- nodemon
- bcrypt
- jwt-simple
- passport
- passport-jwt
- passport-local
- cors
// install mongoDB on OSX
- brew update
- brew install mongodb
// create the data directory inside the server directory
- mkdir -p /data/db
- sudo chown -R $USER /data/db
//chown: change owndership and takes ownership of the directory we just created
- mongod
// ------------------------------------------
//server/.gitignore
node_modules
config.js
// ------------------------------------------
//server/config.js - hold application secrets and config
module.exports = {
secret: 'laiejrfia34ta34995235235'
}
// ------------------------------------------
//server/package.json - adding nodemon
"scripts": {
"dev": "nodemon index.js"
}
// ------------------------------------------
//server/index.js - main starting point
const express = require('express'); //parse response + routing
const http = require('http'); // native node library
const bodyParser = require('body-parser'); // express middleware - parse incoming request into json regardless what request type is
const morgan = require('morgan'); //express middleware - logging framework for logging all incoming requests
const app = express(); //create an instace of express
const router = require('./router');
const mongoose = require('mongoose');
const cors = require('cors');
// db setup
mongoose.connect('mongodb://localhost:auth/auth') //creates a new mongoDB database called auth
// app setup
app.use(morgan('combined'));
app.use(cors());
app.use(bodyParser.json({type: '*/*'}));
router(app);
// server setup
const port = process.env.PORT || 3090;
const server = http.createServer(app);
server.listen(port);
console.log('server listening on', port);
// ------------------------------------------
// server/router.js
const Authentication = require('./controllers/authentication');
const passportService = require('./services/passport');
const passport = require('passport');
const requireAuth = passport.authenticate('jwt', {session: false});
const requireSignin = passport.authenticate('local', {session: false});
module.exports = function(app) {
app.get('/', requireAuth, function(req, res) {
res.send({message: 'super secret code is ABC123'});
});
app.post('/signin', requireSignin, Authentication.signin);
app.post('/signup', Authentication.signup);
}
// ------------------------------------------
// controllers
// server/controllers/authentication.js
const jwt = require('jwt-simple');
const config = require('../config');
const User = require('../models/user');
function tokenForUser(user) {
const timestamp = new Date().getTime();
// sub: short for subject - who does this token belongs to
// iat: issued at time
return jwt.encode({ sub: user.id, iat: timestamp }, config.secret); // first arg: info we want to encode, second: secret string
}
exports.signin = function(req, res, next) {
// user has already had their email and password auth'd
// we just need to give them a token
res.send({ token: tokenForUser(req.user) });
}
exports.signup = function(req, res, next) {
const email = req.body.email;
const password = req.body.password;
if (!email || !password) {
return res.status(422).send({ error: 'You must provide email and password' });
}
// see if a user with the given email exists
User.findOne({ email: email }, function(err, existingUser) {
if (err) { return next(err); }
// if a user with email does exist, return an error
if (existingUser) {
return res.status(422).send({ error: 'Email is in use' }) //unprocessible entity
}
// if a user with email does NOT exist, create and save user record
const user = new User({
email: email,
password: password
});
user.save(function(err) {
if (err) { return next(err); }
});
// respond to request indicating the user was created
// JWT: JSON Web Token - User ID + Our Secet String = JWT
// JWT + Our Secret String = User ID
res.json({ token: tokenForUser(user) });
})
}
// ------------------------------------------
// data management
// server/models/user.js
const mongoose = require('mongoose') //working with mongoDB
const Schema = mongoose.Schema;
const bcrypt = require('bcrypt-nodejs')
// define our model with types
const userSchema = new Schema({
email: { type: String, unique: true, lowercase: true},
password: String
})
// on Save, Hook, encrypt password
// pre: before saving a model, run this function
userSchema.pre('save', function(next) {
// get access to the user model. user is an instance of the user model
const user = this;
// generate a salt then run callback
bcrypt.genSalt(10, function(err, salt) {
if (err) { return next(err) };
// hash (encrypt) our password using the salt
bcrypt.hash(user.password, sale, null, function(err, hash) {
if (err) { return next(err) };
// overwrite plain text password with encrypted password
user.password = hash;
next();
});
});
});
userSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) { return cb(err); }
cb(null, isMatch);
})
}
// create the model class
const ModelClass = mongoose.model('user', userSchema);
// export the model
module.exports = ModelClass;
// ------------------------------------------
// passport authentication management
// server/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');
// create local strategy for logging in with email and password
// using email instead of username
const localOptions = {usernameField: 'email'};
const localLogin = new LocalStrategy(localOptions, function(email, password, done) {
// verify this email and password, call done with the user
// if it is the correct email and password
// otherwise, call done with false
User.findOne({email: email}, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
// compare passwords - is`password equal to user.password
user.comparePassword(password, function(err, isMatch) {
if (err) { return done(err); }
if (!isMatch) { return done(null, false); }
return done(null, user);
})
})
});
// set up options for Jwt strategy
const jwtOptions = {
jwtFromRequest: ExtractJwt.fromHeader('authorization'),
secretOrKey: config.secret
};
// create Jwt strategy
//payload: decoded jwt token (userId, sub), done: callback to call depending whether authentication is successful
const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) {
// see if the userId in the payload exists in our database
// if it does, call 'done' with that user object
// otherwise, call done without a user object
User.findById(payload.sub, function(err, user) {
if (err) return done(err, false); //err: error object, 2nd argument: user object, false if not found
if (user) {
done(null, user);
} else {
done(null, false); // did a search but couldn't find a user
}
})
})
// tell passport to use this strategy
passport.use(jwtLogin);
passport.use(localLogin);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment