Report
Today I got user authentication working with passport. I started setting by setting up my password hashing. I noticed that in the passport documentation that they were using methods on the user object to compare and hash passwords and had an realization. I never made the connection to do store the hashing logic on the model but I loved the pattern. Password hashing is intrinsically tied to the user object and it makes sense to store them together, it also make maintenance easier. With this in mind, I made a static and instance method on my player schema to handle password hashing.
// db/models/player/index.js
const Player= new Schema({...});
Player.methods.checkPassword = function(plainTextPassword) {
return bcrypt.compare(plainTextPassword, this.password)
}
Player.statics.hashPassword = function(plainTextPassword) {
return bcrypt.hash(plainTextPassword, 10)
}
module.exports = Player
You can see the static method being used in the seeder. And the instance method in the passport local strategy.
// db/seeder.js
const player = await db.models.Player.create({
username: "admin",
email: "ephriamhenderson@ephriamhenderson.dev",
// Static method that hashes user password.
password: await db.models.Player.hashPassword("password")
});
// helpers/init-passport.js
const player = await db.models.Player.findOne({
email: email.toLowerCase()
});
// ...
// The instance method that compares the password.
let isMatch = await player.checkPassword(password);
Getting passport working properly took a bit of debugging. First the local strategy didn't seem to be running. Soon I realized that I needed to define the name of my username and password field.
// helpers/init-passport.js
// broken
passport.use(
new LocalStrategy(
async (email, password, done) => {
logger.debug(`Auth attempt for: ${email}`);
const db = await dbPromise;
const player = await db.models.Player.findOne({
email: email.toLowerCase()
});
if (!player) {
logger.debug(`No email found for: ${email}`);
return done(null, false, { message: "Incorrect Credentials" });
}
let isMatch = await player.checkPassword(password);
if (!isMatch) {
logger.debug(`Incorrect password for: ${email}`);
return done(null, false, { message: "Incorrect Credentials" });
}
logger.debug(`${email} logged in successfully.`);
return done(null, player);
}
)
);
// helpers/init-passport.js
// fixed
passport.use(
new LocalStrategy(
{
// define the name of the username and password fields
usernameField: "email",
passwordField: "password"
},
async (email, password, done) => {
logger.debug(`Auth attempt for: ${email}`);
// Make sure the db is connected and and the db object returned
const db = await dbPromise;
const player = await db.models.Player.findOne({
email: email.toLowerCase()
});
// Does player with that email exist? If not send an error.
if (!player) {
logger.debug(`No email found for: ${email}`);
return done(null, false, { message: "Incorrect Credentials" });
}
let isMatch = await player.checkPassword(password);
// Does the password match? If not send and error.
if (!isMatch) {
logger.debug(`Incorrect password for: ${email}`);
return done(null, false, { message: "Incorrect Credentials" });
}
logger.debug(`${email} logged in successfully.`);
return done(null, player);
}
)
);
Once I got the local strategy to run correctly I encountered an issue with my login post route.
// router/users/index.js
router.post(
"/login",
bodyParser.urlencoded({ extended: true }),
// This should redirect depending on failure or success.
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/users/login"
})
);
Passport wouldn't redirect correctly, when I made a request it would hang without throwing an error. Almost as if I had forgot to send a response in a normal route. Eventually I found the problem in my passport serialization.
// helpers/init-passport.js
// broken
passport.serializeUser((user, done) => {
logger.debug(`Serializing ${user.email}`);
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
logger.debug(`Deserializing user: ${id}`);
const db = await dbPromise;
let user = db.models.Player.findById(id);
});
// helpers/init-passport.js
// fixed
passport.serializeUser((user, done) => {
logger.debug(`Serializing ${user.email}`);
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
logger.debug(`Deserializing user: ${id}`);
const db = await dbPromise;
let user = db.models.Player.findById(id);
// I had forgot to call done.
done(null, user);
});
I'm happy with today. I had a good time debugging this issue and I got authentication working fairly easily with passport. As a junior dev I still have a ton to learn about security and would be interested in learning how to make my passwords more secure, am I making any blatant security mistakes? Any tips for best practices? Feel free to tell me how bad I am at this!
Project
[100days] The DND Character Sheet App
This is the first project of my 100 days of coding This is an app to keep D&D character sheets.
Stack
I'll be using Node.js and building a full-stack Express app with MongoDB.
Requirements
Minimum Viable
-
Present a D&D Character Sheet
- The sheet should display all the same info as the first page of the 5e Official sheet.
- Users should be able to log in and create player-characters.
- Users should be able to edit character sheets.
- Users should be able to organize character sheets into groups (parties/tables)
-
Sheets should auto calculate basic stats like ability modifiers
- Support Proficiency Bonuses
Cake
-
Extend character creation to allow the user to use any of the three common stat gen methods
- Point Buy
- Standard Array
- Roll
- Extend the character sheet to all the info in the 5e official sheet.
- Allow for image uploads for character portraits.
- Allow for…
The First project will be an app to keep D&D character sheets.
Stack
I'll be using Node.js and building a full-stack Express app with MongoDB.
Requirements
Minimum Viable
- [ ] Investigate automating or finding a source of info for the data in the SRD.
- [ ] Present a D&D Character Sheet
- [ ] The sheet should display all the same info as the first page of the 5e Official sheet.
- [ ] Users should be able to log in and create player-characters.
- [ ] Users should be able to edit character sheets.
- [ ] Users should be able to organize character sheets into groups (parties/tables)
- [ ] Sheets should auto calculate basic stats like ability modifiers.
- [ ] Support Proficiency Bonuses
Cake
- [ ] Extend character creation to allow the user to use any of the three common stat gen methods.
- [ ] Point Buy
- [ ] Standard Array
- [ ] Roll
- [ ] Extend the character sheet to all the info in the 5e official sheet.
- [ ] Allow for image uploads for character portraits.
- [ ] Allow for extended descriptions/backstories.
- [ ] Characters should have nice full page backstories.
- [ ] Preferably use a markdown editor.
Top comments (0)