Difference between revisions of "Authentication and Authorization in Node"

From Logic Wiki
Jump to: navigation, search
(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 ==
−
=== User.js ===
+
=== /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

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}...`));