Skip to content

Instantly share code, notes, and snippets.

@dylants
Last active February 6, 2021 17:50
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save dylants/8030433 to your computer and use it in GitHub Desktop.
Save dylants/8030433 to your computer and use it in GitHub Desktop.
Passport security using local authentication (username/password)
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"));
});
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"
});
});
};
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"
});
}
});
});
}));
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);
@MatiasArriola
Copy link

Hey, came here through google and I found this very useful.
I spotted a little 'bug' there:

User.findOne({
  username: new RegExp(username, "i")
}

I guess if you have a user called dylants, that query will match dylants when trying to login as dyl, resulting in invalid logins and unexpected behavior.

Hope it helps, thanks.

@kulakowka
Copy link

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