Last active
February 6, 2021 17:50
-
-
Save dylants/8030433 to your computer and use it in GitHub Desktop.
Passport security using local authentication (username/password)
This file contains hidden or 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
require("express-namespace"); | |
var express = require("express"), | |
fs = require("fs"), | |
cons = require("consolidate"), | |
app = express(), | |
passport = require("passport"), | |
mongoose = require("mongoose"); | |
// 30 days for session cookie lifetime | |
var SESSION_COOKIE_LIFETIME = 1000 * 60 * 60 * 24 * 30; | |
// Verifies the user is authenticated, else returns unauthorized | |
var requireAuthentication = function(req, res, next) { | |
if (req.isAuthenticated()) { | |
return next(); | |
} | |
// send the error as JSON to be nice to clients | |
res.send(401, { | |
error: "Unauthorized" | |
}); | |
}; | |
// configure the app (all environments) | |
app.configure(function() { | |
var mongoUrl; | |
// set the port | |
app.set("port", 3000); | |
// configure view rendering (underscore) | |
app.engine("html", cons.underscore); | |
app.set("view engine", "html"); | |
app.set("views", __dirname + "/views"); | |
// use express' cookie parser to access request cookies | |
app.use(express.cookieParser()); | |
// use express' body parser to access body elements later | |
app.use(express.bodyParser()); | |
// use express' cookie session | |
app.use(express.cookieSession({ | |
secret: "super secret", | |
cookie: { | |
maxAge: SESSION_COOKIE_LIFETIME | |
} | |
})); | |
// Configure mongo | |
mongoUrl = "mongodb://localhost/dbname"; | |
mongoose.connect(mongoUrl, function(error) { | |
// handle the error case | |
if (error) { | |
console.error("Failed to connect to the Mongo server!!"); | |
console.error(error); | |
throw error; | |
} else { | |
console.log("connected to mongo server at: " + mongoUrl); | |
} | |
}); | |
// bring in all models into scope (these use mongoose) | |
fs.readdirSync("models").forEach(function(modelName) { | |
require("./models/" + modelName); | |
}); | |
// include passport authentication (after mongo since it requires it) | |
require("./passport-configuration"); | |
app.use(passport.initialize()); | |
app.use(passport.session()); | |
// configure that all routes under /api require authentication | |
app.all("/api/*", requireAuthentication); | |
// pull in all the controllers (these contain routes) | |
fs.readdirSync("controllers").forEach(function(controllerName) { | |
require("./controllers/" + controllerName)(app); | |
}); | |
// lock the router to process routes up to this point | |
app.use(app.router); | |
// static assets processed after routes | |
app.use("/assets", express.static(__dirname + "/public")); | |
}); | |
// configuration for development environment | |
app.configure("development", function() { | |
console.log("in development environment"); | |
app.use(express.errorHandler()); | |
}); | |
// configuration for production environment (NODE_ENV=production) | |
app.configure("production", function() { | |
console.log("in production environment"); | |
// configure a generic 500 error message | |
app.use(function(err, req, res, next) { | |
res.send(500, "An error has occurred"); | |
}); | |
}); | |
// start the app | |
app.listen(app.get("port"), function() { | |
console.log("Express server listening on port " + app.get("port")); | |
}); |
This file contains hidden or 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
var passport = require("passport"); | |
module.exports = function(app) { | |
app.post("/login", function(req, res, next) { | |
// calls passport's local strategy to authenticate | |
passport.authenticate("local", function(err, user, info) { | |
// if any problems exist, error out | |
if (err) { | |
return next(err); | |
} | |
if (!user) { | |
return res.send(500, info.message); | |
} | |
// log in the user | |
req.logIn(user, function(err) { | |
if (err) { | |
return next(err); | |
} | |
// once login succeeded, return the user and session created 201 | |
return res.send(201, user); | |
}); | |
})(req, res, next); | |
}); | |
app.get("/logout", function(req, res) { | |
req.logout(); | |
res.send(200, { | |
status: "OK" | |
}); | |
}); | |
}; |
This file contains hidden or 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
var passport = require("passport"), | |
LocalStrategy = require("passport-local").Strategy, | |
mongoose = require("mongoose"), | |
User = mongoose.model("User"); | |
// Creates the data necessary to store in the session cookie | |
passport.serializeUser(function(user, done) { | |
done(null, user.id); | |
}); | |
// Reads the session cookie to determine the user from a user ID | |
passport.deserializeUser(function(id, done) { | |
User.findById(id, function(err, user) { | |
done(err, user); | |
}); | |
}); | |
// The strategy used when authenticating a user | |
passport.use(new LocalStrategy(function(username, password, done) { | |
// find the user based off the username (case insensitive) | |
User.findOne({ | |
username: new RegExp(username, "i") | |
}).select("+password").exec(function(err, user) { | |
// if any problems, error out | |
if (err) { | |
return done(err); | |
} | |
if (!user) { | |
return done(null, false, { | |
message: "Unknown user: " + username | |
}); | |
} | |
// verify if the password is valid | |
user.isPasswordValid(password, function(err, isValid) { | |
// if any problems, error out | |
if (err) { | |
return done(err); | |
} | |
// only return the user if the password is valid | |
if (isValid) { | |
return done(null, user); | |
} else { | |
return done(null, false, { | |
message: "Invalid password" | |
}); | |
} | |
}); | |
}); | |
})); |
This file contains hidden or 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
var passport = require("passport"), | |
bcrypt = require("bcrypt"), | |
mongoose = require("mongoose"), | |
Schema = mongoose.Schema; | |
var SALT_ROUNDS = 10; | |
// Hide the password by default | |
var UserSchema = new Schema({ | |
username: String, | |
password: { | |
type: String, | |
select: false | |
} | |
}); | |
// never save the password in plaintext, always a hash of it | |
UserSchema.pre("save", function(next) { | |
var user = this; | |
if (!user.isModified("password")) { | |
return next(); | |
} | |
// use bcrypt to generate a salt | |
bcrypt.genSalt(SALT_ROUNDS, function(err, salt) { | |
if (err) { | |
return next(err); | |
} | |
// using the generated salt, use bcrypt to generate a hash of the password | |
bcrypt.hash(user.password, salt, function(err, hash) { | |
if (err) { | |
return next(err); | |
} | |
// store the password hash as the password | |
user.password = hash; | |
next(); | |
}); | |
}); | |
}); | |
UserSchema.methods.isPasswordValid = function(rawPassword, callback) { | |
bcrypt.compare(rawPassword, this.password, function(err, same) { | |
if (err) { | |
callback(err); | |
} | |
callback(null, same); | |
}); | |
}; | |
mongoose.model("User", UserSchema); |
I encountered a similar problem. Here is the solution
User.findOne({ username: username.toLowerCase() }).exec(callback)
And user schema (for example):
var User = mongoose.Schema({
username: {
type: String,
index: true,
trim: true,
minlength: 3,
maxlength: 20,
required: true,
lowercase: true,
unique: true
},
password: {
type: String,
required: true
}
})
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, came here through google and I found this very useful.
I spotted a little 'bug' there:
I guess if you have a user called
dylants
, that query will matchdylants
when trying to login asdyl
, resulting in invalid logins and unexpected behavior.Hope it helps, thanks.