Difference between revisions of "Authentication and Authorization in Node"
From Logic Wiki
(Created page with "Category:Node.js == Model == === User.js === <pre class="brush:js;"> const config = require('config'); const jwt = require('jsonwebtoken'); const Joi = require('joi'); co...") |
|||
| Line 1: | Line 1: | ||
[[Category:Node.js]] | [[Category:Node.js]] | ||
| − | + | == NPM Packages == | |
| + | === lodash === | ||
| + | npm i lodash | ||
| + | const _ = require('lodash'); | ||
| + | _.pick(user, ['name', 'email']) // gets user object and return a new object with only name and email fields in it. (to hide password from returning object) | ||
| + | === joi-password-complexity === | ||
| + | npm i joi-password-complexity | ||
| + | === hash password === | ||
| + | npm i bcrypt | ||
| + | const bcrypt = require('bcrypt'); | ||
| + | ==== Salt ==== | ||
| + | cosnt salt = await bcrypt.genSalt(10) | ||
| + | const hash = await bcrypt.hash('password', salt); | ||
== Model == | == Model == | ||
| − | === | + | === /models/user.js === |
<pre class="brush:js;"> | <pre class="brush:js;"> | ||
const config = require('config'); | const config = require('config'); | ||
| Line 54: | Line 66: | ||
</pre> | </pre> | ||
| + | == Register == | ||
| + | === /router/user.js === | ||
| + | <pre class="brush:js;"> | ||
| + | const auth = require('../middleware/auth'); | ||
| + | const jwt = require('jsonwebtoken'); | ||
| + | const config = require('config'); | ||
| + | const bcrypt = require('bcrypt'); | ||
| + | const _ = require('lodash'); | ||
| + | const {User, validate} = require('../models/user'); | ||
| + | const mongoose = require('mongoose'); | ||
| + | const express = require('express'); | ||
| + | const router = express.Router(); | ||
| + | router.get('/me', auth, async (req, res) => { | ||
| + | const user = await User.findById(req.user._id).select('-password'); | ||
| + | res.send(user); | ||
| + | }); | ||
| + | |||
| + | router.post('/', async (req, res) => { | ||
| + | const { error } = validate(req.body); | ||
| + | if (error) return res.status(400).send(error.details[0].message); | ||
| + | |||
| + | let user = await User.findOne({ email: req.body.email }); | ||
| + | if (user) return res.status(400).send('User already registered.'); | ||
| + | |||
| + | user = new User(_.pick(req.body, ['name', 'email', 'password'])); | ||
| + | const salt = await bcrypt.genSalt(10); | ||
| + | user.password = await bcrypt.hash(user.password, salt); | ||
| + | await user.save(); | ||
| + | |||
| + | const token = user.generateAuthToken(); | ||
| + | res.header('x-auth-token', token).send(_.pick(user, ['_id', 'name', 'email'])); | ||
| + | }); | ||
| + | |||
| + | module.exports = router; | ||
| + | |||
| + | </pre> | ||
| + | |||
| + | == Login == | ||
| + | === /router/auth.js === | ||
<pre class="brush:js;"> | <pre class="brush:js;"> | ||
| + | const Joi = require('joi'); | ||
| + | const bcrypt = require('bcrypt'); | ||
| + | const _ = require('lodash'); | ||
| + | const {User} = require('../models/user'); | ||
| + | const mongoose = require('mongoose'); | ||
| + | const express = require('express'); | ||
| + | const router = express.Router(); | ||
| + | |||
| + | router.post('/', async (req, res) => { | ||
| + | const { error } = validate(req.body); | ||
| + | if (error) return res.status(400).send(error.details[0].message); | ||
| + | |||
| + | let user = await User.findOne({ email: req.body.email }); | ||
| + | if (!user) return res.status(400).send('Invalid email or password.'); | ||
| + | |||
| + | const validPassword = await bcrypt.compare(req.body.password, user.password); | ||
| + | if (!validPassword) return res.status(400).send('Invalid email or password.'); | ||
| + | |||
| + | const token = user.generateAuthToken(); | ||
| + | res.send(token); | ||
| + | }); | ||
| + | |||
| + | function validate(req) { | ||
| + | const schema = { | ||
| + | email: Joi.string().min(5).max(255).required().email(), | ||
| + | password: Joi.string().min(5).max(255).required() | ||
| + | }; | ||
| + | |||
| + | return Joi.validate(req, schema); | ||
| + | } | ||
| + | |||
| + | module.exports = router; | ||
| + | </pre> | ||
| + | == Middleware == | ||
| + | === /middleware/auth.js === | ||
| + | <pre class="brush:js;"> | ||
| + | const jwt = require('jsonwebtoken'); | ||
| + | const config = require('config'); | ||
| + | |||
| + | module.exports = function (req, res, next) { | ||
| + | const token = req.header('x-auth-token'); | ||
| + | if (!token) return res.status(401).send('Access denied. No token provided.'); | ||
| + | |||
| + | try { | ||
| + | const decoded = jwt.verify(token, config.get('jwtPrivateKey')); | ||
| + | req.user = decoded; | ||
| + | next(); | ||
| + | } | ||
| + | catch (ex) { | ||
| + | res.status(400).send('Invalid token.'); | ||
| + | } | ||
| + | } | ||
| + | </pre> | ||
| + | === /middleware/admin.js === | ||
| + | <pre class="brush:js;"> | ||
| + | |||
| + | module.exports = function (req, res, next) { | ||
| + | // 401 Unauthorized | ||
| + | // 403 Forbidden | ||
| + | |||
| + | if (!req.user.isAdmin) return res.status(403).send('Access denied.'); | ||
| + | |||
| + | next(); | ||
| + | } | ||
| + | </pre> | ||
| + | |||
| + | == index.js == | ||
| + | |||
| + | <pre class="brush:js;"> | ||
| + | const config = require('config'); | ||
| + | const Joi = require('joi'); | ||
| + | Joi.objectId = require('joi-objectid')(Joi); | ||
| + | const mongoose = require('mongoose'); | ||
| + | const genres = require('./routes/genres'); | ||
| + | const customers = require('./routes/customers'); | ||
| + | const movies = require('./routes/movies'); | ||
| + | const rentals = require('./routes/rentals'); | ||
| + | const users = require('./routes/users'); | ||
| + | const auth = require('./routes/auth'); | ||
| + | const express = require('express'); | ||
| + | const app = express(); | ||
| + | |||
| + | if (!config.get('jwtPrivateKey')) { | ||
| + | console.error('FATAL ERROR: jwtPrivateKey is not defined.'); | ||
| + | process.exit(1); | ||
| + | } | ||
| + | |||
| + | mongoose.connect('mongodb://localhost/vidly') | ||
| + | .then(() => console.log('Connected to MongoDB...')) | ||
| + | .catch(err => console.error('Could not connect to MongoDB...')); | ||
| + | |||
| + | app.use(express.json()); | ||
| + | app.use('/api/genres', genres); | ||
| + | app.use('/api/customers', customers); | ||
| + | app.use('/api/movies', movies); | ||
| + | app.use('/api/rentals', rentals); | ||
| + | app.use('/api/users', users); | ||
| + | app.use('/api/auth', auth); | ||
| + | const port = process.env.PORT || 3000; | ||
| + | app.listen(port, () => console.log(`Listening on port ${port}...`)); | ||
</pre> | </pre> | ||
Revision as of 12:23, 7 November 2018
Contents
NPM Packages
lodash
npm i lodash
const _ = require('lodash');
_.pick(user, ['name', 'email']) // gets user object and return a new object with only name and email fields in it. (to hide password from returning object)
joi-password-complexity
npm i joi-password-complexity
hash password
npm i bcrypt
const bcrypt = require('bcrypt');
Salt
cosnt salt = await bcrypt.genSalt(10)
const hash = await bcrypt.hash('password', salt);
Model
/models/user.js
const config = require('config');
const jwt = require('jsonwebtoken');
const Joi = require('joi');
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
email: {
type: String,
required: true,
minlength: 5,
maxlength: 255,
unique: true
},
password: {
type: String,
required: true,
minlength: 5,
maxlength: 1024
},
isAdmin: Boolean
});
userSchema.methods.generateAuthToken = function() {
const token = jwt.sign({ _id: this._id, isAdmin: this.isAdmin }, config.get('jwtPrivateKey'));
return token;
}
const User = mongoose.model('User', userSchema);
function validateUser(user) {
const schema = {
name: Joi.string().min(5).max(50).required(),
email: Joi.string().min(5).max(255).required().email(),
password: Joi.string().min(5).max(255).required()
};
return Joi.validate(user, schema);
}
exports.User = User;
exports.validate = validateUser;
Register
/router/user.js
const auth = require('../middleware/auth');
const jwt = require('jsonwebtoken');
const config = require('config');
const bcrypt = require('bcrypt');
const _ = require('lodash');
const {User, validate} = require('../models/user');
const mongoose = require('mongoose');
const express = require('express');
const router = express.Router();
router.get('/me', auth, async (req, res) => {
const user = await User.findById(req.user._id).select('-password');
res.send(user);
});
router.post('/', async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let user = await User.findOne({ email: req.body.email });
if (user) return res.status(400).send('User already registered.');
user = new User(_.pick(req.body, ['name', 'email', 'password']));
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
await user.save();
const token = user.generateAuthToken();
res.header('x-auth-token', token).send(_.pick(user, ['_id', 'name', 'email']));
});
module.exports = router;
Login
/router/auth.js
const Joi = require('joi');
const bcrypt = require('bcrypt');
const _ = require('lodash');
const {User} = require('../models/user');
const mongoose = require('mongoose');
const express = require('express');
const router = express.Router();
router.post('/', async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let user = await User.findOne({ email: req.body.email });
if (!user) return res.status(400).send('Invalid email or password.');
const validPassword = await bcrypt.compare(req.body.password, user.password);
if (!validPassword) return res.status(400).send('Invalid email or password.');
const token = user.generateAuthToken();
res.send(token);
});
function validate(req) {
const schema = {
email: Joi.string().min(5).max(255).required().email(),
password: Joi.string().min(5).max(255).required()
};
return Joi.validate(req, schema);
}
module.exports = router;
Middleware
/middleware/auth.js
const jwt = require('jsonwebtoken');
const config = require('config');
module.exports = function (req, res, next) {
const token = req.header('x-auth-token');
if (!token) return res.status(401).send('Access denied. No token provided.');
try {
const decoded = jwt.verify(token, config.get('jwtPrivateKey'));
req.user = decoded;
next();
}
catch (ex) {
res.status(400).send('Invalid token.');
}
}
/middleware/admin.js
module.exports = function (req, res, next) {
// 401 Unauthorized
// 403 Forbidden
if (!req.user.isAdmin) return res.status(403).send('Access denied.');
next();
}
index.js
const config = require('config');
const Joi = require('joi');
Joi.objectId = require('joi-objectid')(Joi);
const mongoose = require('mongoose');
const genres = require('./routes/genres');
const customers = require('./routes/customers');
const movies = require('./routes/movies');
const rentals = require('./routes/rentals');
const users = require('./routes/users');
const auth = require('./routes/auth');
const express = require('express');
const app = express();
if (!config.get('jwtPrivateKey')) {
console.error('FATAL ERROR: jwtPrivateKey is not defined.');
process.exit(1);
}
mongoose.connect('mongodb://localhost/vidly')
.then(() => console.log('Connected to MongoDB...'))
.catch(err => console.error('Could not connect to MongoDB...'));
app.use(express.json());
app.use('/api/genres', genres);
app.use('/api/customers', customers);
app.use('/api/movies', movies);
app.use('/api/rentals', rentals);
app.use('/api/users', users);
app.use('/api/auth', auth);
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Listening on port ${port}...`));