Tuesday, February 28, 2017

#1.5 Node.js Basics - authentication + JWT

This part is focused on authentication and json web token (jwt):



1. Small change in 'app.js' file in order to initialize passport middleware (in bold):

const express    = require('express');
const path       = require('path');
const bodyParser = require('body-parser');
const cors       = require('cors');
const passport   = require('passport');
const mongoose   = require('mongoose');

const users      = require('./routes/users');
const dbconfig   = require('./config/database');

/* Connect to database */
mongoose.connect(dbconfig.database, (err) => {
    if(err){
        console.log('Database connection error: ', err); 
    }
});
mongoose.connection.on('connected', () => {
    console.log('Connected to database ' + dbconfig.database);
});

/* Express initialization */
const app = express();

/* Server port number */
const port = 3000;

/* Middleware - CORS */
app.use(cors());

/* Set static folder */
app.use(express.static(path.join(__dirname, 'public')));

/* Middleware - Body parser */
app.use(bodyParser.json());

/* Middleware - Passport */
app.use(passport.initialize());
app.use(passport.session());

require('./config/passport')(passport);

/* Middleware - Routes */
app.use('/users', users);

/* Root */
app.get('/', (req, res) => {
    res.send('Invalid endpoint');
})

/* Start server */
app.listen(port, () => {
    console.log('Server started on port '+port);
})


2. Create a new file '/config/passport.js' with the code below:

const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt  = require('passport-jwt').ExtractJwt;
const User        = require('../models/user');
const config      = require('../config/database');

module.exports = function (passport) {
    let opts = { };
    opts.jwtFromRequest = ExtractJwt.fromAuthHeader();
    opts.secretOrKey    = config.secret;

    passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
        User.getUserById(jwt_payload._doc._id, (err, user) => {
            if(err){
                return done(err, false);
            }

            if(user){
                return done(null, user);
            } else {
                return done(null, false);
            }
        })
    }))
}


3. Implement the '/users/authenticate' router in '/routes/users.js' (changes are in bold):

const express  = require('express');
const router   = express.Router();

const passport = require('passport');
const jwt      = require('jsonwebtoken');
const User     = require('../models/user');
const config   = require('../config/database');


router.post('/register', (req, res) => {
    let newUser = new User({
        name: req.body.name,
        email: req.body.email,
        username: req.body.username,
        password: req.body.password
    });

    User.addUser(newUser, (err, user) => {
        if(err){
            res.json({success: false, msg: 'Failed to register user' });
        } else {
            res.json({success: true, msg: 'User registered' });
        }
    });
})

router.post('/authenticate', (req, res) => {
    const username = req.body.username;
    const password = req.body.password;

    User.getUserByUsername(username, (err, user) => {
        if(err) throw err;

        if(!user) {
            return res.json({success: false, msg: 'User not found'});
        } 

        User.comparePassword(password, user.password, (err, isMatch) => {
            if(err) throw err;

            if(isMatch){
                const token = jwt.sign(user, config.secret, {
                    expiresIn: 604800 //1 week
                })

                res.json({ 
                    success: true, 
                    token: 'JWT '+token, 
                    user: {
                        id: user._id,
                        name: user.name,
                        username: user.username,
                        email: user.email
                    }
                });
            } else {
                return res.json({success: false, msg: 'Wrong password'});
            }
        })
    })
})

router.get('/profile', passport.authenticate('jwt', {session:false}), (req, res) => {
    res.json({user: req.user})
})

module.exports = router;


4. Extend the user model in '/model/user.js' by adding the function 'comparePassword' as below (in bold):

const mongoose = require('mongoose');
const bcrypt   = require('bcryptjs');
const config   = require('../config/database');

// User Schema
const userSchema = mongoose.Schema({
    name:     { type: String },
    email:    { type: String, required: true },
    username: { type: String, required: true },
    password: { type: String, required: true }
});

const user = module.exports = mongoose.model('User', userSchema);

module.exports.getUserById = function (id, callback) {
    user.findById(id, callback)
}

module.exports.getUserByUsername = function (username, callback) {
    const query = { username: username };
    user.findOne(query, callback);
}

module.exports.addUser= function (newUser, callback) {
    bcrypt.genSalt(10, (err, salt) => {
        bcrypt.hash(newUser.password, salt, (err, hash) => {
            if(err) throw err;
            newUser.password = hash;
            newUser.save(callback);
        })
    })
}

module.exports.comparePassword = function (candidatePassword, hash, callback) {
    bcrypt.compare(candidatePassword, hash, (err, isMatch) => {
        if(err) throw err;
        callback(null, isMatch);
    })
}


5. Test the service using Postman (Chrome extension):


Method: Post;
URL: localhost:3000/users/authenticate
Headers -> Content-Type: application/json
Body: raw ->
{
"username": "john",
"password": "1234"
}









References:
- https://www.npmjs.com/package/passport-jwt
- https://youtu.be/6pdFXmTfkeE?list=PLillGF-RfqbZMNtaOXJQiDebNXjVapWPZ
-

No comments:

Post a Comment