Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Passport JWT strategy for mobile #110

Merged
merged 13 commits into from
Sep 11, 2017

Conversation

JameelB
Copy link
Contributor

@JameelB JameelB commented Sep 1, 2017

Motivation

The current strategy that Passport is using for mobile is not working properly due to the implications listed on RAINCATCH-1192.

Description

Add Passport's JWT strategy for mobile authentication instead of cookies.

Progress

  • Separate mobile and portal into two express Routers
  • Create relevant routes for each mobile/portal
  • Add passport JWT Strategy
  • Update Passport Auth's authenticate function to authenticate without redirection
  • Update Passport Auth's protect function to verify user authentication with a token
  • Clean up
  • Update unit tests
  • Update documentation

Additional Notes

Related JIRA - https://issues.jboss.org/browse/RAINCATCH-1209

@@ -28,12 +31,16 @@ export class PassportAuth implements EndpointSecurity {
* @param app - An express application
* @param sessionOpts - Session options to be used by express-session
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add note that when skipping this session is not being used.

@@ -111,7 +140,23 @@ export class PassportAuth implements EndpointSecurity {
* @param passportApi - passport.js instance
*/
protected setup(passportApi: passport.Passport) {
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeader(),
secretOrKey: 'secret'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to extract and document that as keeping this secret is crucial for general application security.

}
});

router.post('/login-mobile', authService.authenticate('jwt'), (req, res, next) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is needed? Probably forgot to remove it?

@wtrocki
Copy link
Member

wtrocki commented Sep 1, 2017

Checked changes locally. Great progress so far.

*/
public init(app: express.Router, sessionOpts?: SessionOptions) {
public init(app: express.Router, sessionOpts?: SessionOptions, secret?: string) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

secret?: any may be better => That's restrictive as people may pass also key (which is probably better to secure this)

};
this.userRepo.getUserByLogin(jwtPayload.username, callback);
}));
protected setup(passportApi: passport.Passport, jwtOpts?: JwtOptions) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing documentation for parameter.

this.userRepo.getUserByLogin(jwtPayload.username, callback);
}));
} else {
passportApi.use(new Strategy(defaultStrategy(this.userRepo, this.userService)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw. - wit this change our defaultStrategy may not be as accurate as before. WDYT to rename this to WebStrategy to reflect the use case?

}));
protected setup(passportApi: passport.Passport, jwtOpts?: JwtOptions) {
if (jwtOpts) {
passportApi.use(new JwtStrategy(jwtOpts, (jwtPayload, done) => {
Copy link
Member

@wtrocki wtrocki Sep 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Optional] [style] If default strategy is extracted to separate file let's have this as well.

};
this.userRepo.getUserByLogin(jwtPayload.username, callback);
}));
protected setup(passportApi: passport.Passport, jwtOpts?: JwtOptions) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Question] Do we still using interface for this? I think we may actually not need
We can have two setup methods once with jwtOptions and second that will setup session based strategy? This will save you another if and will make code cleaner.
We can even add name to provide more context.

setupToken and setupCookie etc.

@@ -19,5 +19,9 @@
},
"redis": {
"url": "redis://127.0.0.1:6379"
},
"jwtSecret": "raincatcher",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will suggest here to put really long generated hash (min 250bits).
It's also important to document this properly.

},
"jwtSecret": "raincatcher",
"clientUrl": {
"portal": "http://localhost:9003/?url=http://localhost:8001"
Copy link
Member

@wtrocki wtrocki Sep 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bug] 💯 That's not goingto work for any other environment apart from local development.
Having it this way will also mean additional setup instruction to this.
Overall -1 for this parameter, but we probably need to talk about it to get proper intentions here.

demoDataSetup(connectionPromise);

const portalApp = express.Router();
portalsecurityMiddleware = securitySetup(portalApp, sessionOpts);
Copy link
Member

@wtrocki wtrocki Sep 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Proposition] As mentioned above we do not use interface. Let's just rename this to reflect what we really setting this up. This method is just helper to setup passport strategy (simplification, hiding complexity)

return res.render('login', {
title: 'Portal Feedhenry Workforce Management'
});
});

router.post('/login', authService.authenticate('local', '/', '/loginError'));
router.post('/login', authService.authenticate('local', {
successReturnToOrRedirect: config.clientUrl.portal,
Copy link
Member

@wtrocki wtrocki Sep 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: Worth reading as inspiration: http://www.baeldung.com/spring-security-redirect-login


router.get('/loginError', (req: express.Request, res: express.Response) => {
return res.render('login', {
title: 'Feedhenry Workforce Management',
title: 'Portal Feedhenry Workforce Management',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Style][Optional] Portal Feedhenry Workforce Management => RainCatcher Portal
[Optional] - Externalize to config.
[Optional] - Move to passport (as they are now really connected to the actual strategies) not universal.


router.get('/loginError', (req: express.Request, res: express.Response) => {
return res.render('login', {
title: 'Feedhenry Workforce Management',
title: 'Portal Feedhenry Workforce Management',
message: 'Invalid credentials'});
});

router.get('/profile', authService.protect(), (req: express.Request, res: express.Response) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Question] Is that used in portal only? Maybe comment.


router.get('/loginError', (req: express.Request, res: express.Response) => {
return res.render('login', {
title: 'Feedhenry Workforce Management',
title: 'Portal Feedhenry Workforce Management',
message: 'Invalid credentials'});
});

router.get('/profile', authService.protect(), (req: express.Request, res: express.Response) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proper format will be user/profile

@wtrocki
Copy link
Member

wtrocki commented Sep 4, 2017

@JameelB - Exceptional work. There is one bug we can deal with and couple a minor improvements.
We can chat about that tommorow and approve this PR.

import { UserRepository } from '../user/UserRepository';
import { UserService } from '../user/UserService';

import { EndpointSecurity } from '@raincatcher/express-auth';
import { defaultStrategy } from './DefaultStrategy';
import { jwtStrategy, webStrategy } from './DefaultStrategy';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Very optional] DefaultStrategy may be plural here : DefaultStrategies.

"jwtSecret": "raincatcher",
"clientUrl": {
"portal": "http://localhost:9003/?url=http://localhost:8001"
"jwtSecret": "D837131FD17F62CB85FBD5919563086369691F4D42379C3596F811839A8992CBA1FBA88DF243BF2481940F112D339C33283BDFEF29A13612550EDAAAB7B5E061",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping @austincunningham - that's update for demo passport auth setup.
Users will need to change that in order to make sure that solution is secure.

wtrocki
wtrocki previously approved these changes Sep 5, 2017
@wtrocki
Copy link
Member

wtrocki commented Sep 5, 2017

@JameelB - Marking as approved for now, but let's not merge until angularjs work will be reviewed and tested.

Unit tests needs minor tweak:

  1) Test Passport Auth should return an error if an error occurred when retrieving the user:
     TypeError: Cannot read property 'authorization' of undefined
      at src/auth/PassportAuth.ts:26:335
      at Context.<anonymous> (test/PassportAuthTest.ts:51:34)
  2) Test Passport Auth should not authenticate the user if the credentials provided are not valid:
     TypeError: Cannot read property 'authorization' of undefined
      at src/auth/PassportAuth.ts:26:335
      at Context.<anonymous> (test/PassportAuthTest.ts:72:34)
  3) Test Passport Auth should authenticate the user if the credentials provided are valid:
     TypeError: Cannot read property 'authorization' of undefined
      at src/auth/PassportAuth.ts:26:335
      at Context.<anonymous> (test/PassportAuthTest.ts:93:34)
  4) Test Passport Auth should not authenticate if the user is already authenticated:
     TypeError: Cannot read property 'authorization' of undefined
      at src/auth/PassportAuth.ts:26:335
      at Context.<anonymous> (test/PassportAuthTest.ts:222:34)

@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 8e531a9 on JameelB:offline-mobile-auth into ** on feedhenry-raincatcher:master**.

1 similar comment
@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 8e531a9 on JameelB:offline-mobile-auth into ** on feedhenry-raincatcher:master**.

@coveralls
Copy link

coveralls commented Sep 8, 2017

Coverage Status

Changes Unknown when pulling 86629f3 on JameelB:offline-mobile-auth into ** on feedhenry-raincatcher:master**.

@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 7c220c4 on JameelB:offline-mobile-auth into ** on feedhenry-raincatcher:master**.

1 similar comment
@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 7c220c4 on JameelB:offline-mobile-auth into ** on feedhenry-raincatcher:master**.

@coveralls
Copy link

coveralls commented Sep 8, 2017

Coverage Status

Changes Unknown when pulling 3ce0a2d on JameelB:offline-mobile-auth into ** on feedhenry-raincatcher:master**.

const token = req.headers.authorization.toString().substring(4);
try {
const user = jwt.verify(token, this.jwtOpts.secretOrKey);
hasRole = role ? this.userService.hasResourceRole(user, role) : true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO role ? A : B makes this code unreadable - try to avoid that in the future, especially in mission critical code like security implementation.

req.session.clientURL = req.session.returnTo;
let hasRole;

if (req.headers && req.headers.authorization) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be wort to check if auth is JWT.

@wtrocki wtrocki changed the title [WIP] Passport JWT strategy for mobile Passport JWT strategy for mobile Sep 10, 2017
@wtrocki wtrocki merged commit 983cd8c into feedhenry-raincatcher:master Sep 11, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

Successfully merging this pull request may close these issues.

None yet

3 participants