secure sails api with passport

secure a sails api with login and jwt authentification using passport

tl;dr execute this script

wget -O sails-auth.sh https://goo.gl/jWHcVv; chmod +x ./sails-auth.sh; ./sails-auth.sh

Sails installation

  • install sails sudo npm -g install sails

  • create new app sails new project --no-frontend

  • go into project dir cd project

  • install required lib
    npm i --save jsonwebtoken passport passport-jwt passport-local bcrypt-nodejs
  • create user controller
    sails generate controller user

Sails Controller, Service, Model and Policy

  • create new service api/services/AuthService.js

    let bcrypt = require('bcrypt-nodejs');
    let jwt = require('jsonwebtoken');
    
    module.exports = {
        hashPassword: function(user) {
            if (user.password) {
            user.password = bcrypt.hashSync(user.password);
            }
        },
        comparePassword: function(password, user) {
            return bcrypt.compareSync(password, user.password);
        },
        createToken: function(user) {
            return jwt.sign({
                user: user.toJSON(),
                },
                sails.config.jwt.secretOrKey,
                {
                algorithm: sails.config.jwt.algorithm,
                expiresIn: sails.config.jwt.expiresInMinutes,
                issuer: sails.config.jwt.issuer,
                audience: sails.config.jwt.audience,
                }
            );
        },
        localStrategy(email, password, next) {
            User.findOne({email: email})
                .exec(function(error, user) {
                if (error) return next(error, false, {});
    
                if (!user) return next(null, false, {
                    code: 'E_USER_NOT_FOUND',
                    message: email + ' is not found'
                });
    
                if (!AuthService.comparePassword(password, user)) {
                    return next(null, false, {
                    code: 'E_WRONG_PASSWORD',
                    message: 'Password is wrong'
                    });
                }
    
                return next(null, user, {});
                });
        },
        jwtStrategy(req, payload, next) {
            let user = payload.user;
            // do your things with user like recording usage of api
            return next(null, user, {});
        },
        passportAuth(req, res, error, user, info) {
            if (error) return res.serverError(error);
            if (!user) {
            return res.forbidden({code: info.code, message: info.message});
            }
            return res.ok({
            token: AuthService.createToken(user),
            user: user,
            });
        }
    };
    
  • create new controler sails generate controller Auth

  • add code to api/controllers/AuthController.js

    /**
    * AuthController
    *
    * @description :: Server-side logic for managing Auths
    * @help        :: See http://sailsjs.org/#!/documentation/concepts/Controllers
    */
    
    let passport = require('passport');
    
    module.exports = {
        register: function(req, res) {
            User
                .create(_.omit(req.allParams(), 'id'))
                .then(function(user) {
                return user;
                })
                .then(res.created)
                .catch(res.serverError);
        },
        login: function(req, res) {
            passport.authenticate('local',
                AuthService.passportAuth.bind(this, req, res))(req, res);
        },
    };
    
  • complete user model in api/models/User.js

    /**
    * User.js
    *
    * @description :: TODO: You might write a short summary of how this model works and what it represents here.
    * @docs        :: http://sailsjs.org/documentation/concepts/models-and-orm/models
    */
    
    let hash = function(values, next) {
        AuthService.hashPassword(values);
        next();
    };
    
    module.exports = {
        attributes: {
            password: {
                type: 'string',
            },
            email: {
                type: 'string',
                email: true,
                required: true,
                unique: true,
            },
        },
        beforeUpdate: hash,
        beforeCreate: hash,
    }
  • create policy api/policies/isAuthenticated.js

    /**
    * isAuthenticated
    * @description :: Policy to inject user in req via JSON Web Token
    */
    const passport = require('passport');
    
    module.exports = function(req, res, next) {
        passport.authenticate('jwt', function(error, user, info) {
            if (error) return res.serverError(error);
            if (!user)
            return res.forbidden({code: info.code, message: info.message});
            req.user = user;
    
            next();
        })(req, res);
    };
    

Sails configuration

  • create new config config/passport.js

    const passport = require('passport');
    const LocalStrategy = require('passport-local').Strategy;
    const JwtStrategy = require('passport-jwt').Strategy;
    const ExtractJwt = require('passport-jwt').ExtractJwt;
    const AuthService = require('../api/services/AuthService');
    
    let EXPIRES_IN_MINUTES = process.env.JWT_EXPIRE_IN_MINUTE || 60 * 24;
    let SECRET = process.env.JWT_SECRET || 'mysecret';
    let ALGORITHM = process.env.JWT_ALGORITHM || 'HS256';
    let ISSUER = process.env.JWT_ISSUER || 'mysite.com';
    let AUDIENCE = process.env.JWT_AUDIENCE || 'mysite.com';
    
    const LOCAL_STRATEGY_CONFIG = {
        usernameField: 'email',
        passwordField: 'password',
        passReqToCallback: false
    };
    
    const JWT_STRATEGY_CONFIG = {
        expiresInMinutes: EXPIRES_IN_MINUTES,
        secretOrKey: SECRET,
        issuer: ISSUER,
        algorithm: ALGORITHM,
        audience: AUDIENCE,
        jwtFromRequest: ExtractJwt.fromAuthHeader(),
        passReqToCallback: true
    };
    
    passport.use( new LocalStrategy(LOCAL_STRATEGY_CONFIG, AuthService.localStrategy));
    passport.use( new JwtStrategy(JWT_STRATEGY_CONFIG, AuthService.jwtStrategy));
    
    module.exports.jwt = JWT_STRATEGY_CONFIG;
  • add policy to route in config/policies.js

    module.exports.policies = {
        '*': ['isAuthenticated'],
        'AuthController': {
            '*': true,
        },
    }
  • enable cors : uncomment allRoutes: true in config/cors.js

  • enable model alter : uncomment migrate: 'alter' in config/models.js

Use authentification

  • run sails sails lift

  • test authentification required ( should get 403 error )

    GET http://localhost:1337/user 
  • register user ( using postman )

    POST http://localhost:1337/auth/register 
    {
            "email": "test@test.com", 
            "password": "test"
    }
  • login user ( using postman )

    POST http://localhost:1337/auth/login 
    {
            "email": "test@test.com", 
            "password": "test"
    }

    You should get a JWT to provide in headers of your futur requests

  • add authentification header

    Authorization: "Bearer YOUR_JWT_TOKEN"
    GET http://localhost:1337/user