mkdir auth (md auth on Windows)
Open up terminal
npm/yarn init
touch server.js
touch .gitignore
For this exercise, we'll need MogoDB to be running
yarn add /npm install express mongoose change main from index.js to server.js
{
"scripts": {
"start": "nodemon server.js"
}
}
const express = require('express')
const mongoose require('mongoose')
mongoose
.connect('mongodb://localhost/authdb')
.then(connection => {console.log('\n=== connected to mongo ===\n')
})
.catch(err => console.log('error connecting to mongo'));
const server = express();
server.get('/', (req, res) => {
res.send({ api: 'running' }) // always want to send something to test it
})
server.listen(8000, () => console.log('\n=== api running on 8k ===\jn'));
express middleware review (global and local)
mongoose middleware (lifecycle hooks)
extending mongoose models with custom 'methods'
hashing user passwords for database storage
persisting state across requests using sessions (in-memory and persisted in mongo)
authenticating users using cookies and sessions
protecting resources from unauthenticated users
authenticating users using JSON Web Tokens (JWTs, pronounced JOT).
persisting tokens on the front-end to keep users authenticated across sessions
security good practices (like don't store passwords as plaintext like T-Mobile was doing)
Global middleware is added to the server and it will be available for all requests. Local middleware runs only before certain requests/routes
function greet(req, res, next) {
req.message = 'Hello World';
next();
}
server.use(greet);
server.get('/', (req, res) => {
res.send({ route: '/', message: req.message });
});
server.get('/hello', (req, res) => {
res.send({ route: 'hello', message: req.message });
});
// server.use(greet);
server.get('/', (req, res) => {
res.send({ route: '/', message: req.message })
}) <--- no longer using greet
server.get('/hello', greet, (req, res) => {
res.send({ route: 'hello', message: req.message })
}); <-- uses greet
What if instead of greet this had been authenticate? And what if instead of adding something there, I did something like ....
function authenticate(req, res, next) {
if (req.body.password === 'mellon') {
next()
} else {
res.status(401).send('You shall not pass!!!')
}
}
server.use(express.json()) // Don't forget to parse the JSON!
server.post('/login', authenticate, (req, res) => {
res.send('Welcome to the Mines of Moria');
});
So if you get to the route, it means you passed the authentication
If you don't use nodemon, it runs with node but when you make a change, the server doesn't restart
Do we have an error on the server? Is the server running? Is the server running using yarn start with nodemon?
client -> request -> [ (middleware) api ({middleware queue} mongoose) ]-> database
They're called lifecycle hooks because they behave the same as the lifecycle hooks from React
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
lowercase: true, // if you pass in Kyle, it will change to kyle
// good for normalizing usernames
},
password: {
type: String,
required: true
}
});
module.exports = mongoose.model('User', userSchema);
server.post('/register', (req, res) => {
const user = new User(req.body);
user
.save()
.then(user => res.status(201).send(user))
.catch(err => res.status(500).send(err))
});
The problem with this code ^ is that password is plaintext
Before we do the save to the database, we want to change the password
With mongoose, we can write a lifecycle hook Lifecycle methods have both a pre- and a -post
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
lowercase: true
};
password: {
type: String,
required: true,
},
});
userSchema.pre('save', function (next) {
console.log('pre save hook') // can't use arrow function here
next()
})
userSchema.post('save', function (next) {
console.log('post save hook') // can't use arrow function here
next()
}) // example that we can execute code after and before save
Encryption is a two-way process
Hashing is a one-way process
need to slow down production of hashes to increase security
Key derivation algorithm:
Add time to it
Go to website: https://www.grc.com/haystack.htm
const bcrypt = require('bcrypt')
userSchema.pre('save', function (next) { // can't use arrow function here because bcrypt uses 'this'
bcrypt.hash(this.password, 11, (err, hash) => { // 11 is the number of rounds (don't use less than 10), regular Node callback
if (err) {
return next(err) // Most examples uses callback because they don't support promises, but some support promises
}
this.password = hash;
return next() // goes on to save to the database
// might work without return, but examples have return
})
})
npmjs.org
If you want to authenticate, can use a function similar to middlware earlier Would then find the matching username in the database, find the stored password, hash the password provided, and compare
Go with software that is widely used - sometimes there is malicious code on npm