Last active
November 23, 2024 11:16
-
-
Save nonlogos/5cb9ce7559cf6a63de84723076f34c28 to your computer and use it in GitHub Desktop.
basic API Server with Node, Express and Passport Authentication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); |
Dee254-cloud
commented
Nov 23, 2024
<script src="https://gist.github.com/nonlogos/5cb9ce7559cf6a63de84723076f34c28.js"></script>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment