Skip to content

Commit

Permalink
feat(core): JWT Authentication simplified
Browse files Browse the repository at this point in the history
Implements JWT Authentication, and removes dependency of session
storage.

Closes meanjs#389
  • Loading branch information
mleanos committed Sep 8, 2016
1 parent 1a274d2 commit 0dd3a14
Show file tree
Hide file tree
Showing 19 changed files with 202 additions and 123 deletions.
6 changes: 6 additions & 0 deletions config/env/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,11 @@ module.exports = {
fileSize: 1 * 1024 * 1024 // Max file size in bytes (1 MB)
}
}
},
jwt: {
secret: process.env.JWT_SECRET || 'M3@N_R0CK5',
options: {
expiresIn: process.env.JWT_EXPIRES_IN || '1d'
}
}
};
35 changes: 35 additions & 0 deletions config/lib/authorization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict';

var config = require('../config'),
jwt = require('jsonwebtoken'),
lodash = require('lodash');

var auth = {
signToken: signToken
};

// Export the token auth service
module.exports = auth;

// Sign the Token
function signToken(user, options) {
var payload,
token,
jwtOptions;

if (!user || !user._id) {
return null;
}

options = options || {};

payload = {
user: user._id.toString()
};

jwtOptions = lodash.merge(config.jwt.options, options);

token = jwt.sign(payload, config.jwt.secret, jwtOptions);

return token;
}
22 changes: 17 additions & 5 deletions config/lib/express.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ var config = require('../config'),
flash = require('connect-flash'),
hbs = require('express-hbs'),
path = require('path'),
_ = require('lodash'),
lusca = require('lusca');
lusca = require('lusca'),
passport = require('passport');

/**
* Initialize local variables
Expand Down Expand Up @@ -88,6 +88,21 @@ module.exports.initMiddleware = function (app) {
// Add the cookie parser and flash middleware
app.use(cookieParser());
app.use(flash());

// Authorize JWT
app.use(function (req, res, next) {
passport.authenticate('jwt', { session: false }, function (err, user) {
if (err) {
return next(new Error(err));
}

if (user) {
req.user = user;
}

next();
})(req, res, next);
});
};

/**
Expand Down Expand Up @@ -237,9 +252,6 @@ module.exports.init = function (db) {
// Initialize modules static client routes, before session!
this.initModulesClientRoutes(app);

// Initialize Express session
this.initSession(app, db);

// Initialize Modules configuration
this.initModulesConfiguration(app);

Expand Down
45 changes: 12 additions & 33 deletions config/lib/socket.io.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ var config = require('../config'),
fs = require('fs'),
http = require('http'),
https = require('https'),
cookieParser = require('cookie-parser'),
passport = require('passport'),
socketio = require('socket.io'),
session = require('express-session'),
MongoStore = require('connect-mongo')(session);
ExtractJwt = require('passport-jwt').ExtractJwt;

// Define the Socket.io configuration method
module.exports = function (app, db) {
Expand Down Expand Up @@ -69,40 +67,21 @@ module.exports = function (app, db) {
// Create a new Socket.io server
var io = socketio.listen(server);

// Create a MongoDB storage object
var mongoStore = new MongoStore({
mongooseConnection: db.connection,
collection: config.sessionCollection
});

// Intercept Socket.io's handshake request
io.use(function (socket, next) {
// Use the 'cookie-parser' module to parse the request cookies
cookieParser(config.sessionSecret)(socket.request, {}, function (err) {
// Get the session id from the request cookies
var sessionId = socket.request.signedCookies ? socket.request.signedCookies[config.sessionKey] : undefined;

if (!sessionId) return next(new Error('sessionId was not found in socket.request'), false);

// Use the mongoStorage instance to get the Express session information
mongoStore.get(sessionId, function (err, session) {
if (err) return next(err, false);
if (!session) return next(new Error('session was not found for ' + sessionId), false);
// Use Passport to populate the user details
passport.initialize()(socket.request, {}, function () {
passport.authenticate('jwt', { session: false }, function (err, user) {
if (err) {
return next(new Error(err));
}

// Set the Socket.io session information
socket.request.session = session;
if (user) {
socket.request.user = user;
}

// Use Passport to populate the user details
passport.initialize()(socket.request, {}, function () {
passport.session()(socket.request, {}, function () {
if (socket.request.user) {
next(null, true);
} else {
next(new Error('User is not authenticated'), false);
}
});
});
});
next();
})(socket.request, socket.request.res, next);
});
});

Expand Down
7 changes: 6 additions & 1 deletion modules/core/client/config/core.client.route-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
routeFilter.$inject = ['$rootScope', '$state', 'Authentication'];

function routeFilter($rootScope, $state, Authentication) {
$rootScope.$on('$stateChangeStart', stateChangeStart);

Authentication.ready
.then(function (auth) {
$rootScope.$on('$stateChangeStart', stateChangeStart);
});

$rootScope.$on('$stateChangeSuccess', stateChangeSuccess);

function stateChangeStart(event, toState, toParams, fromState, fromParams) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
.module('core')
.factory('authInterceptor', authInterceptor);

authInterceptor.$inject = ['$q', '$injector', 'Authentication'];
authInterceptor.$inject = ['$q', '$injector'];

function authInterceptor($q, $injector, Authentication) {
function authInterceptor($q, $injector) {
var service = {
responseError: responseError
};
Expand All @@ -19,7 +19,7 @@
switch (rejection.status) {
case 401:
// Deauthenticate the global user
Authentication.user = null;
$injector.get('Authentication').user = null;
$injector.get('$state').transitionTo('authentication.signin');
break;
case 403:
Expand Down
2 changes: 1 addition & 1 deletion modules/core/client/services/socket.io.client.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
function connect() {
// Connect only when authenticated
if (Authentication.user) {
service.socket = io();
service.socket = io('', { query: 'auth_token=' + Authentication.token });
}
}

Expand Down
2 changes: 1 addition & 1 deletion modules/core/client/views/header.client.view.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
</li>
<li class="divider"></li>
<li>
<a href="/api/auth/signout" target="_self">Signout</a>
<a ng-click="vm.authentication.signout();">Signout</a>
</li>
</ul>
</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

$http.post('/api/auth/signup', vm.credentials).success(function (response) {
// If successful we assign the response to the global user model
vm.authentication.user = response;
Authentication.login(response.user, response.token);

// And redirect to the previous or home page
$state.go($state.previous.state.name || 'home', $state.previous.params);
Expand All @@ -55,7 +55,7 @@

$http.post('/api/auth/signin', vm.credentials).success(function (response) {
// If successful we assign the response to the global user model
vm.authentication.user = response;
Authentication.login(response.user, response.token);

// And redirect to the previous or home page
$state.go($state.previous.state.name || 'home', $state.previous.params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
vm.passwordDetails = null;

// Attach user profile
Authentication.user = response;
Authentication.login(response.user, response.token);

// And redirect to the index page
$location.path('/password/reset/success');
Expand Down
61 changes: 58 additions & 3 deletions modules/users/client/services/authentication.client.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,68 @@
.module('users.services')
.factory('Authentication', Authentication);

Authentication.$inject = ['$window'];
Authentication.$inject = ['$window', '$state', '$http', '$location', '$q', 'UsersService'];

function Authentication($window, $state, $http, $location, $q, UsersService) {
var readyPromise = $q.defer();

function Authentication($window) {
var auth = {
user: $window.user
user: null,
token: null,
login: login,
signout: signout,
refresh: refresh,
ready: readyPromise.promise
};

// Initialize service
init();

return auth;

function init() {
var token = localStorage.getItem('token') || $location.search().token || null;
// Remove the token from the URL if present
$location.search('token', null);

if (token) {
auth.token = token;
$http.defaults.headers.common.Authorization = 'JWT ' + token;
refresh();
} else {
readyPromise.resolve(auth);
}
}

function login(user, token) {
auth.user = user;
auth.token = token;

localStorage.setItem('token', token);
$http.defaults.headers.common.Authorization = 'JWT ' + token;

readyPromise.resolve(auth);
}

function signout() {
localStorage.removeItem('token');
auth.user = null;
auth.token = null;

$state.go('home', { reload: true });
}

function refresh(requestFromServer, callback) {
readyPromise = $q.defer();

UsersService.me().$promise
.then(function (user) {
auth.user = user;
readyPromise.resolve(auth);
})
.catch(function (errorResponse) {
readyPromise.reject(errorResponse);
});
}
}
}());
4 changes: 4 additions & 0 deletions modules/users/client/services/users.client.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
return $resource('api/users', {}, {
update: {
method: 'PUT'
},
me: {
method: 'GET',
url: 'api/users/me'
}
});
}
Expand Down
32 changes: 32 additions & 0 deletions modules/users/server/config/strategies/jwt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

/**
* Module dependencies
*/
var passport = require('passport'),
JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt,
User = require('mongoose').model('User');

module.exports = function (config) {
var opts = {
jwtFromRequest: ExtractJwt.versionOneCompatibility({ tokenQueryParameterName: 'auth_token' }),
secretOrKey: config.jwt.secret
// opts.issuer = "accounts.examplesoft.com",
// opts.audience = "yoursite.net"
};

passport.use(new JwtStrategy(opts, function (jwt_payload, done) {
User.findById({ _id: jwt_payload.user }, '-salt -password', function (err, user) {
if (err) {
return done(err, false);
}

if (!user) {
return done('User not found');
}

return done(null, user);
});
}));
};
4 changes: 2 additions & 2 deletions modules/users/server/config/strategies/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ module.exports = function () {
return done(err);
}
if (!user || !user.authenticate(password)) {
return done(null, false, {
return done({
message: 'Invalid username or password'
});
}, false);
}

return done(null, user);
Expand Down
15 changes: 0 additions & 15 deletions modules/users/server/config/users.server.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,11 @@ var passport = require('passport'),
* Module init function
*/
module.exports = function (app, db) {
// Serialize sessions
passport.serializeUser(function (user, done) {
done(null, user.id);
});

// Deserialize sessions
passport.deserializeUser(function (id, done) {
User.findOne({
_id: id
}, '-salt -password', function (err, user) {
done(err, user);
});
});

// Initialize strategies
config.utils.getGlobbedPaths(path.join(__dirname, './strategies/**/*.js')).forEach(function (strategy) {
require(path.resolve(strategy))(config);
});

// Add passport's middleware
app.use(passport.initialize());
app.use(passport.session());
};
Loading

0 comments on commit 0dd3a14

Please sign in to comment.