Skip to content
This repository has been archived by the owner on Aug 30, 2021. It is now read-only.

[UNRESOLVED] JWT Authentication #1163

Closed
wants to merge 28 commits into from

Conversation

trainerbill
Copy link
Contributor

I took a stab at redoing the Authentication in favor of JWT. I am using passport-jwt to leverage the existing authentication structure. The Authentication service now holds a user and token and uses localStorage to store the token. I am only storing the token. If you refresh the page the AuthService will look for a token in local storage and if it exists hits the users/me route to get the user. The auth service also sets the $http header for Authorization if the token exists so you don't have to add it to other services. Currently working:

  1. Login
  2. Sign Up
  3. Persistent on Refresh and users/me route
  4. Edit Profile

I want to get buy in from contributors before going any further. This PR will touch a lot of files and I don't want to get all the way done and someone has a problem and this ends up in PR hell. If I can get some feedback and +1's I will have this done by Friday this week. Also would like a volunteer to help fix the tests when this is done and ready. Thanks to @mleanos for the server side kick start.

@mleanos @lirantal @ilanbiala @codydaig

Fixes #389
Fixes #1086

},
jwt: {
secret: process.env.TOKEN_AUTH_SECRET || 'M3@N_R0CK5!',
expiresInSeconds: process.env.TOKEN_AUTH_EXPIRES_SECONDS || 200 * 60 * 60 // 200 hours
Copy link
Member

Choose a reason for hiding this comment

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

Why 200 hours?

@ilanbiala
Copy link
Member

Did a quick read through, didn't comment on everything. @trainerbill can you give the pros and cons of JWT and any limitations of this implementation?

@mleanos
Copy link
Member

mleanos commented Jan 27, 2016

@trainerbill This looks pretty good. I'm gonna pull it down and play around with it.

The other day, I started looking at using the passport-http-bearer module. I think the module you're using is probably better, but what are the advantages of this one over the bearer?

One thing I started playing around with, mostly in thought, was to think of MEAN Token-auth as just another Provider. If we use a passport module for authenticating the JWT Token, can we merely treat it as a new Provider, and have it behave the same (just as Google, Twitter, GitHub, etc)?

Think of having a MEAN.JS logo mixed in with the other providers at time of sign in or sign up. The project user could decide if they want to allow either authentication method, or both.

It think it could allow the project to be easily switched to and from JWT & Session based authentication. Any thoughts on that approach?

@trainerbill
Copy link
Contributor Author

@ilanbiala Thanks for the feedback. This is more of a rough draft than a code review so don't worry about new lines etc. I am just looking for an overall discussion on the topic. My goal is to get away from the current AuthService that reads from window.user and JWT is kinda the norm right now. JWT is becoming the defacto standard for authenticating REST services. The biggest pro is that it will work with mobile. Session based auth will not. A mobile app could be coded to hit the auth/sigin URL store the token and then make additional calls to the MEAN backend. Most applications are going away from Session based authentications as they are not mobile friendly. I am sure there are other benefits but those are the 2 I can think of off hand. With JWT the client holds the keys instead of the server. As for limitations, I am not aware of any. Perfomance should be the same as it is the same amount of DB calls, just an added token decode which isn't much.

@mleanos The module you linked looks more like an oAuth strategy. I like the passport-jwt strategy as it works with our current auth procedure and you can make it route based. So if I have a public endpoint I don't do the passport.authenticate. How I implemented it really is just another provider.

https://github.com/trainerbill/mean/blob/53302087a0fc1d2d38a867ca342d707786ca2bd2/modules/users/server/config/strategies/jwt.js

As far as switching between session and JWT, I don't see the point. If you look at how passport and express currently work, only the _id is stored and there is a DB call to get the rest of the info:

https://github.com/meanjs/mean/blob/master/modules/users/server/config/users.server.config.js#L21

So the JWT approach is the same DB calls, as you decode the token, get the ID and find the user. Plus it has the added benefit of being client independent. Mobile devices, and any other client would be able to authenticate and use the REST interfaces.

For your question on if we can treat it as another provider, the answer would probably be no. You still need a user/pass combo to authenticate a user and the token is merely a reflection of that authentication. The "local" strategy now just checks the user/pass combo and returns the user info and the token. This will be how providers work when I get to them. The same steps as far as auth, but when it comes back we send a token to the AuthService and everything works as normal.

Like the discussion, lets keep it up. Want everything ironed out before doing more work!

@codydaig
Copy link
Member

I'm totally onboard with this PR. I like token auth vs. session auth. It makes scaling the app to more than 1 server much easier.

@codydaig codydaig added this to the 0.5.0 milestone Jan 27, 2016
@codydaig codydaig self-assigned this Jan 27, 2016
@trainerbill
Copy link
Contributor Author

@codydaig awesome, I will continue to work on this then. I think it will solve a lot of the other open issues as well

What do you all think of changing up the routes a bit:

https://github.com/trainerbill/mean/blob/JWTAuthorization/modules/users/server/routes/users.server.routes.js

Pros:

  1. Can do router.all() and hit all of the routes on the specific router. Like the JWT auth
  2. Currently using app.param like we are doing can cause collisions if a submodule wants to use say :userId
  3. Keeps the route logic contained in the module instead of the app.

Cons:

  1. Its different
  2. have to require express and create a new router.
  3. Probably going to need separate routes for admin. /api/admin/users etc.

I personally like it, but want some feedback.

@codydaig
Copy link
Member

I personally am not usually a fan of the express router because it usually adds unnecessary complexity. However, I think it could be of benefit here to help keep things more modular. I think that is a huge undertaking though because I would like to keep it consistent throughout the framework.

@trainerbill
Copy link
Contributor Author

@codydaig Yeah I know... but this whole PR is gonna be a huge undertaking... I know in one of my apps the app.param collision screwed me.

@codydaig
Copy link
Member

@trainerbill Doing things in smaller PRs is what I usually like to go for. It makes it easier to review, discuss, and get merged. My vote would be to have the router refactor be its own PR.

@trainerbill
Copy link
Contributor Author

@codydaig True. However adding passport.authenticate(jwt) to every route is going to be kind of a pain.

@ilanbiala
Copy link
Member

@trainerbill if possible, @codydaig's suggestion of keeping changes in smaller PRs will definitely speed up the whole conversion process. @mleanos has a PR (#1121) that seems to also implement JWT instead of sessions and his is slightly smaller in impact. @trainerbill can you and @mleanos can combine PRs to create a better one that has less impact and incorporates the changes of both?

@trainerbill
Copy link
Contributor Author

@ilanbiala I understand that smaller PR's are easier and will try to keep it as small as possible, but this is a large change. I have spoken with @mleanos and offered for him to assist. His current PR isn't close to being complete, and will need 90% of the changes in this PR.

On the plus side I am almost done. I need some help on the chat module. Anyone familiar enough with the module and socket.io to make the auth changes? I found this article but I am not at all familiar with this module or socket.io

http://wmyers.github.io/technical/nodejs/Simple-JWT-auth-for-SocketIO/

@mleanos
Copy link
Member

mleanos commented Jan 27, 2016

@trainerbill I can help out with the Socket.IO changes

My intentions with my PR was to keep the server-side implementation independent from the client-side. Really those two things are separate. It looks like most of your server-side changes are the same that I implemented in my PR. My suggestion from the beginning of working on this, was to work on a shared branch; with client-side & server-side changes made independent of one another.

So shall I submit PR's to your branch?

@trainerbill
Copy link
Contributor Author

@mleanos Absolutely. I don't think you can really keep client/server in independent PR's. In order to make sure that it is working you have to remove the session authentication. Otherwise the app will continue to use session and JWT so you really don't know if the changes you are making are working as expected with JWT.

I will be AWK till a little later tonight and will check back in. Can some of you pull the branch and take it for a spin? It should be in a semi workable state. Whats currently working:

  1. Authentication. Signin/Signup/Signout
  2. User Routes. Edit Profile, Password, Picture
  3. Article Module. Add/Create/Update/List
  4. User Admin
  5. Social Login
  6. Client/Server Tests
  7. Password Reset
  8. Chat Module

Whats not working:

  1. Chat Module
  2. Possibly user admin. Not sure on this if someone wants to check and see
  3. Signout bug. When on a user profile and sign out it should be forbidden. Possibly just redirect to home on signout?
  4. Social Accounts. I need some client/secrets if anyone has them.
  5. Tests. REally need help here.
  6. Password Reset
  7. New tests for client Auth service

@mleanos If you are working on specific items let me know and I will stay away from them.

Thanks Everyone!

@codydaig
Copy link
Member

@trainerbill socketio-jwt is what I've used in the past.

@jameslegue
Copy link

+1 on this. Have been waiting for token based auth for awhile now (main use case is sharing of REST APIs with mobile).

@trainerbill trainerbill changed the title JWT Authentication [WIP] feat(JWT Authentication) Jan 28, 2016
@trainerbill
Copy link
Contributor Author

Can someone look into CSRF with JWT? I don't think we need the CSRF PR's after this goes in.

@trainerbill
Copy link
Contributor Author

Anyone have any thoughts on the following file:

config/lib/jwtAuthentication.js

I think it should go in the users module since that is where the authentication is taking place. Just not sure where to put it, controllers?, and what to name it.

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

resolve(token);

Copy link
Member

Choose a reason for hiding this comment

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

extra newline

@trainerbill
Copy link
Contributor Author

All. I have this PR about ready. I will do the Password Reset this afternoon:

  1. @codydaig I made a frame of the auth service test in modules/users/tests/client/authentication.client.service.tests.js. The describes and its are there but I don't know enough about the client side tests to finish this. Can you do it or find someone who can?
  2. @mleanos How is the chat module coming along?
  3. @ilanbiala @rhutchison You can do a first round code review now if you want to.

The coverall tests decreased because it looks like there is a duplicate in the user tests

@@ -60,8 +60,8 @@ module.exports = {
callbackURL: '/api/auth/github/callback'
},
paypal: {
clientID: process.env.PAYPAL_ID || 'CLIENT_ID',
clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET',
clientID: process.env.PAYPAL_ID || 'AVWxUS4uQVfXAzKdCQKrIkczdPxixgKQ-SA7Ewodn9UmLOjHSCkftB6D9Wj_lVjRZpF9VCanp7thMCeG',
Copy link
Member

Choose a reason for hiding this comment

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

These probably shouldn't be here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

lol. good thing that wasn't production...

@trendzetter
Copy link
Contributor

@trainerbill I managed to run the branch. As soon as nodemon has started got this error:

/meanTB/node_modules/mongoose/node_modules/mongodb/lib/utils.js:98
    process.nextTick(function() { throw err; });
                                        ^
TypeError: undefined is not a function
    at allFailed (/meanTB/node_modules/passport/lib/middleware/authenticate.js:154:11)
    at attempt (/meanTB/node_modules/passport/lib/middleware/authenticate.js:160:28)
    at JwtStrategy.strategy.fail (/meanTB/node_modules/passport/lib/middleware/authenticate.js:277:9)
    at JwtStrategy.authenticate (/meanTB/node_modules/passport-jwt/lib/strategy.js:114:21)
    at attempt (/meanTB/node_modules/passport/lib/middleware/authenticate.js:341:16)
    at authenticate (/meanTB/node_modules/passport/lib/middleware/authenticate.js:342:7)
    at /meanTB/config/lib/socket.io.js:88:59
..

Am I doing it wrong or did I find an issue to resolve?

@trainerbill
Copy link
Contributor Author

@trendzetter Tests still pass so this should not be an issue. MY guess is that you have a dependency issue. Pull the branch and make sure you NPM Install

@trendzetter
Copy link
Contributor

You are correct. I forgot in all the rush to get this going..

@trendzetter
Copy link
Contributor

I tested this and hope to see this merged. It would improve greatly on security.
There is proposed to break this pr up in pieces. @lirantal Maybe you could do a suggestion? I propose to spit the social network accounts into a separate pr because that part still seems to have some bugs while the rest of the functionality is working fully now.

@lirantal
Copy link
Member

@trainerbill ok, a lot has been going on in this PR so I'd need to free up some time to get it resolved in several PRs.

@@ -37,5 +37,12 @@ module.exports = {
fileSize: 1*1024*1024 // Max file size in bytes (1 MB)
}
}
},
Copy link

Choose a reason for hiding this comment

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

jwt: { secret: process.env.TOKEN_AUTH_SECRET || 'M3@N_R0CK5!', options: { //Anything From https://www.npmjs.com/package/jsonwebtoken expiresIn: process.env.TOKEN_EXPIRES || '1d' } } Basically not a big deal but gives a formatting warning.

@zargold
Copy link

zargold commented Feb 22, 2016

@trainerbill maybe you know if you tested for this?/If I messed it up ? screen shot 2016-02-22 at 12 31 11 pm
Seems like even after logging out (read: JWT token is gone) session storage is still there. Not sure if I messed up his copy though so maybe someone should test more thoroughly.
screen shot 2016-02-22 at 12 36 17 pm
Cookies are there probably how it knew my name and I was still able to post.
And yes if I delete cookie then try again I can't create/read/update 403 forbidden. AKA you can post with just JWT or just cookie, and cookie is not being deleted at least for me.

@trendzetter
Copy link
Contributor

I am getting forbidden when trying to access articles/create when signed out.

@trendzetter
Copy link
Contributor

If I use 2 tabs and log out in one I can continue my session with my token in the other tab as long as I do not refresh the page.

@@ -85,7 +85,7 @@ module.exports = function (app, db) {

Copy link

Choose a reason for hiding this comment

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

The stuff above here talks about sessionSecret sessionId not sure if that's need/was causing my problem.

Copy link

Choose a reason for hiding this comment

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

I removed the above code (everything about Session and cookies and everything worked just fine! Except the tests I guess... so might have to change those

@trainerbill
Copy link
Contributor Author

FYI one issue is that signout has to kill the token in the $http header.

@0xNacho
Copy link

0xNacho commented Feb 24, 2016

@trainerbill I've just read your comment about CSRF protection. If meanjs no longer use cookies, you don't need to worry about cross requests. In other words, with JWT you don't need CSRF protection.
Actually, JWT token replaces CSRF security token in each request.

http://stackoverflow.com/questions/21357182/csrf-token-necessary-when-using-stateless-sessionless-authentication

@jameslegue
Copy link

@trainerbill Saw your comment above you are working on a fork with JWTAuth and Material design. Any chance you're willing to share that branch? It's exactly what I'm after.

@trainerbill
Copy link
Contributor Author

@jameslegue I do. Hit me up on gitter

@ilanbiala
Copy link
Member

Let's rebase this and finalize everything. @mleanos @trainerbill @codydaig can we work out the rest of the kinks?

@avingochea
Copy link

Hey guys, I think I've found an issue. req.user is undefined in articles.server.policy.js so it always defaults to 'guest' role.

@avingochea
Copy link

Fixed it by adding this to the articles.server.routs.js file just before the routes are declared

// Set JWT Auth for all article routes router.all('*', passport.authenticate('jwt', { session: false }));

@avingochea
Copy link

Just realized that the get route intentionally doesn't have authentication. What do you guys think about authenticating if there's a token, but continuing if there isn't. This would leave the policy to decide whether the route should be accessible to guests

@mleanos
Copy link
Member

mleanos commented Apr 9, 2016

@avingochea IMO, we should attempt to authenticate on each request. Thus, the responsibility of API access will solely reside in the server policies.

@trainerbill
Copy link
Contributor Author

That adds to much logic to the policy. Just attached the passport Middleware to the route that you want to secure like it's designed to do.

@lirantal
Copy link
Member

lirantal commented Sep 8, 2016

@trainerbill this by far adds so much code that I'm incapable of code reviewing that.
We can take a look at this in a new PR if there's an easier approach?

@lirantal lirantal closed this Sep 8, 2016
@lirantal lirantal changed the title feat(core): JWT Authentication [UNRESOLVED] JWT Authentication Sep 8, 2016
@mleanos mleanos modified the milestones: Backlog, 0.5.0, q Oct 2, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MEAN.js 0.4.0 with token authentication? User logged out, but still appears to be logged in