Passport-jwt authenticate not working well with node-jwt-simple - node.js

I'm using passport-jwt to authenticate some routes and I'm creating my jwts with node-jwt-simple/jwt-simple but facing some difficulties cause it looks like my passport-jwt authenticate middleware is not being called at all.
Here is my
passport-jwt-strategy
const jwtOpts = {
jwtFromRequest: ExtractJwt.fromHeader('Authorization'),
secretOrKey: secret,
};
passport.use(new jwtStrategy(jwtOpts, (payload, done) => {
console.log('payload ', payload.sub);
User.findById(payload.sub, (err, user) => {
if(err) { return done(err); }
if(!user) { console.log('didnt find!'); return done(null, false); }
done(null, user);
});
}));
which i'm then integrating it over here.
routes file
router.get('/success',
passport.authenticate('jwt', {session: false}),
async (ctx, next) => ctx.body = await "success!");
Here is also the way I make my jwt.
function tokenForUser(user) {
const timeStamp = new Date().getTime;
return jwt.encode({sub: user._id, iat: timeStamp}, secret);
}
//- Later in signup process
userToSave.save(async(err, user) => {
if(err) { return next(err); }
const token = await tokenForUser(user);
next(token);
});
//- If this helps, here is how my secret file looks like.
const secret = "JKAha23ja1ddHdjjf31";
export default secret;
Problem comes, when I hit that route i only get Unauthorized and in the console nothing gets logged out not even the 'payload' key I specified first.
I should also say that I have the token at ctx.request.get('Authorization') (Koa based) i think it's something like req.header('Authorization') with express in all routes.
Also The exact express based problem can be found on the github issues of node-jwt-simple here incase there is any problem with my code samples.
Thank you.

After I wrapped my head right i knew that this has been my horrible understanding of how the whole authentification process works.
When I decoded the token from ctx.get('Authorization') I got a different _id than the one stored in the db Because I had hardcoded Authorization header in postman and thought "If I ctx.set('Authorization', token); It will replace the one I hardcoded on postman".
Less did I think that this jwt will be included in a header of requests when I make http calls on front end.
I naively thought jwts are passed directly from the server to the browser (Something like how render works) and Not from the server to an ajax process which later embeds it in request made which is the correct way.
The whole code is awesome, except now I have to just pass the token ctx.body = token; after I created it when I signed up.
Thank You.

Related

Can't make custom named local strategies work in PassportJS

So recently I needed to split my local strategy so it fits our new needs for different types of users logging to our website. Our code up to date looks like:
const localLogin = new LocalStrategy(localOptions, function(email, password, done) {
// Verify this email and password, call done with the user
// if it is correct email and password
// otherwise, call done with false
});
passport.use(localLogin);
And then in our users file we call authenticate like so:
const requireSignin = passport.authenticate('local', {session: false});
router.post('/signin', requireSignin, (request, response, next) => {
// User already had their email and password Auth'd
// Give him a token
});
We've just changed a few things to leave it like so:
passport.use('local.one', localLogin);
And then call it as follows:
const requireSignin = passport.authenticate('local.one', {session: false});
Tried everything we read in different answers but we can't get it to work. Seems so close yet so far... please help.
P.D.: We've also tried to get a response like:
passport.authenticate('local.one', function(err, user, info) {
console.log('HERE')
if (err) return next(err);
if (!user) return next(null, false);
next(null, user);
})
But we don't get any data from that.
Ok, after a lot of testing and digging a few conclusions just in case someone finds this same stone on their way:
LOGS! wow that was helpful...
After initialize passport on the express() object call this line to load custom config file:
require('./yourpathtoconfigfileHERE/yourconfigfile')(passport);
Also here is a link that pointed me in the right way:
Passport: Unknown authentication strategy "local"

Facebook-passport with JWT

I've been using Passport on my server for user authentication.
When a user is signing in locally (using a username and password), the server sends them a JWT which is stored in localstorage, and is sent back to server for every api call that requires user authentication.
Now I want to support Facebook and Google login as well. Since I began with Passport I thought it would be best to continue with Passport strategies, using passport-facebook and passport-google-oauth.
I'll refer to Facebook, but both strategies behave the same. They both require redirection to a server route ('/auth/facebook' and '/auth/facebook/callback' for that matter).
The process is successful to the point of saving users including their facebook\google ids and tokens on the DB.
When the user is created on the server, a JWT is created (without any reliance on the token received from facebook\google).
... // Passport facebook startegy
var newUser = new User();
newUser.facebook = {};
newUser.facebook.id = profile.id;
newUser.facebook.token = token; // token received from facebook
newUser.facebook.name = profile.displayName;
newUser.save(function(err) {
if (err)
throw err;
// if successful, return the new user
newUser.jwtoken = newUser.generateJwt(); // JWT CREATION!
return done(null, newUser);
});
The problem is that after its creation, I don't find a proper way to send the JWT to the client, since I should also redirect to my app.
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false,
successRedirect : '/',
failureRedirect : '/'
}), (req, res) => {
var token = req.user.jwtoken;
res.json({token: token});
});
The code above redirects me to my app main page, but I don't get the token.
If I remove the successRedirect, I do get the token, but I'm not redirected to my app.
Any solution for that? Is my approach wrong? Any suggestions will do.
The best solution I found for that problem would be to redirect to the expected page with a cookie which holds the JWT.
Using res.json would only send a json response and would not redirect. That's why the other suggested answer here would not solve the problem I encountered.
So my solution would be:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false,
successRedirect : '/',
failureRedirect : '/'
}), (req, res) => {
var token = req.user.jwtoken;
res.cookie('auth', token); // Choose whatever name you'd like for that cookie,
res.redirect('http://localhost:3000'); // OR whatever page you want to redirect to with that cookie
});
After redirection, you can read the cookie safely and use that JWT as expected. (you can actually read the cookie on every page load, to check if a user is logged in)
As I mentioned before, it is possible to redirect with the JWT as a query param, but it's very unsafe.
Using a cookie is safer, and there are still security solutions you can use to make it even safer, unlike a query param which is plainly unsecure.
Adding to Bar's answer.
I prepared a landing component to extract the cookie, save it to local storage, delete the cookie, then redirect to an authorized page.
class SocialAuthRedirect extends Component {
componentWillMount() {
this.props.dispatch(
fbAuthUser(getCookie("auth"), () => {
document.cookie =
"auth=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
this.props.history.push("/profile");
})
);
}
render() {
return <div />;
}
}
A proper solution would be to implement the redirection on the client side.
Simply use:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false,
failureRedirect: '/login'
}), (req, res) => {
res.json({
token: req.user.jwtoken
})
}
)
If you're client side receives the token, then redirect from there to home page, and in the case the login wasn't successful, it would be redirected by the server directly.
Or you can go for the full client side management as I would:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false
}), (req, res) => {
if (req.user.jwtoken) {
res.json({
success: true,
token: req.user.jwtoken
})
} else {
res.json({
success: false
})
}
}
)
If success === true, store JWT in LocalStorage, else redirect to login page.

Save Token in local Storage using node

I'm using JWT ("jsonwebtoken": "^5.4.0") with express 4 and jade.
I'm able to create the right Token, but How can i Pass this token in each call?
Where I have to store this token ? in headers or in localStorage?
For now I'm using CURL with Postman, and Set token in header in
x-access-token
Have I Do create a middleware that retrieve a token from Database and use this in each call?
thanks
You do not need to save and check the token from the database. This token such a mechanism can be decoded with only your-server, and if it was done that the token is valid. The code that you want to do should look like.
var cookieParser = require('cookie-parser')
app.use(cookieParser())
app.get('/login', function(req, res, next) {
var user = {name:'test'}; //!! find the user and check user from db then
var token = jwt.sign(user, 'secret', {
expiresInMinutes: 1440
});
res.cookie('auth',token);
res.send('ok');
});
app.use(function(req, res, next) {
var token = req.cookies.auth;
// decode token
if (token) {
jwt.verify(token, 'secret', function(err, token_data) {
if (err) {
return res.status(403).send('Error');
} else {
req.user_data = token_data;
next();
}
});
} else {
return res.status(403).send('No token');
}
});
Here you can find very nice article : https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens
I would recommend checking this out if you want local storage: https://www.npmjs.com/package/node-localstorage
But, with that said, you guys and girls wouldn't believe how long it took me to find res.cookie('auth' token) from the above answer. I scoured Google for hours, Passport docs, Express docs, GraphQL and authentication/authorization docs in an effort to find out how to get the token to the API in a stateless manner.
I already built JWT token security and secured my GraphQL resolvers with it, but then, I opted to use EJS along with graphql-request (approx same as Apollo Client), so I needed to find a way to pass the token to my middleware without using a server side session.
Storing a JWT token in cookies is fine especially if you take extra precautions such as signing the cookie, and I recall there are also options you can include that keep the cookie secure, so that other sites cannot see it if the "browser" allows access to cookies. If a cookie is signed with your server secret, the data inside the cookie simply cannot be altered and still be valid. The risk is always still someone leaking their token/cookie, and if that bothers you, do research into refresh tokens. However, API tokens are generally and should be kept tightly secret and safe. Your biggest annoyance will more likely be the requirement to maintain a blacklist of JWTs that expire a year from now if you set expiry to 1y.
I am just including my findings here because this question is actually a rare resource it seems...
Here is my Express middleware for authentication:
// AUTHENTICATION
app.use(async (req) => {
try {
const token = req.headers.authorization || req.cookies.auth
const { person } = await jwt.verify(token, SECRET)
req.person = person
return req.next()
} catch (e) {
return req.next()
}
})
You can see I am setting the token from the header with cookie as fallback. This supports my needs fine and allows me to use really any client with stateless security.
My logged in user is available as req.person in my views and GraphQL resolvers. If req.person is not set, the user is treated as not-logged-in.
I am using return req.next() which is important to note because calling next() without parameters is treated as "clean go-to next middleware and/or proceed to process request". If you include any string or object parameter, it will throw an error that can bubble down to your error handling middleware. You can try it yourself. Put return next('You are not authenticated.') in the catch block and you will see it halt the request before your route.
I use return next() because I handle authorization in the routes and in my resolvers. It allows more flexibility such as facilitating register and login mutations to be accessed by non-authenticated users.
Here is my GraphQL endpoint (I am using Apollo Server):
app.use('/graphql', bodyParser.json(), graphqlExpress((req) => {
const context = {
person: req.person
}
return {
schema,
context,
rootValue: null
}
}))
In my GraphQL resolvers, the third parameter of every query has context.person populated with req.person which comes from the above Authentication middleware.
That is really all a person needs to know.
Here is how I am using the NPM package called graphql-request:
https://www.npmjs.com/package/graphql-request
app.get('/allpeople', async (req, res) => {
try {
const client = new GraphQLClient(GRAPHQL_ENDPOINT, {
headers: { Authorization: req.headers.authorization || req.cookies.auth }
})
const query = `query allPeople($serialNumber: String!) {
allPeople(serialNumber: $serialNumber) {
id
created
status
email
}
}`
const variables = {
serialNumber: req.person
}
const response = await client.request(query, variables)
res.render('allpeople/list', { people: response.allPeople })
} catch (e) {
throw [`allPeople`, `${JSON.stringify(error, null, 2)}`]
}
})
I include this code because there are no "more advanced" example usages of graphql-request, and I like it so far. It is very concise and could easily be swapped out for Apollo Client if you venture into React.js. My examples here are also very relevant for anyone researching createNetworkInterface and new ApolloClient().

SailsJS Linkedin OAuth 2.0 Login Flow Issues

I'm trying to use this library to authenticate using Linkedin:
https://github.com/auth0/passport-linkedin-oauth2
No Linkedin Login Prompt
I have configured my Passport Linkedin Strategy like so:
var passport = require('passport');
var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
passport.use(new LinkedInStrategy({
clientID: 'LINKEDIN_API_KEY',
clientSecret: 'LINKEDIN_API_SECRET',
callbackURL: 'http://localhost:1337/auth/linkedin/callback',
scope: ['r_emailaddress', 'r_basicprofile'],
state: true
}, function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's LinkedIn profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the LinkedIn account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
}));
My AuthController.js looks like this:
var passport = require('passport');
module.exports = {
login: function(req, res) {
passport.authenticate('linkedin', function(err, user, info) {
// The request will be redirected to LinkedIn for authentication, so this
// function will not be called.
});
},
callback: function(req, res) {
// ------------------------------------------------------------------------
// after user authenticated, we get the user's email from
// Linkedin's JSON response and save it against the matching  
// email address in the User model
// ------------------------------------------------------------------------
console.log(res);
},
logout: function(req, res) {
req.logout();
res.send('logout successful');
}
};
From the linkedin oauth library, I expect the call to:
passport.authenticate('linkedin', function...);
In my AuthController's login action, to redirect the user to Linkedin's login prompt page but what I am actually seeing is my browser just keeps on loading, loading, loading and never stops.
Am I doing something wrong ?
Some questions I am not sure of:
Does Linkedin expect my server to be running on HTTPS before it lets this whole thing starts working ?
Is there some special configurations that I need to do in my Linkedin developers app setting ? (I've enabled all the correct Javascript SDK URLs)
Callback Error
OK, so continuing on, my next problem appears to be here:
return done(null, profile);
^
TypeError: object is not a function
My code is following the npm module instruction here: https://www.npmjs.com/package/passport-linkedin-oauth2
Maybe SailsJS has another way of writing it yet again....
Authentication Always Fails
After fixing the callback error as mentioned in my solution below, I decided to keep moving on and see how it goes despite the Linkedin documentation isn't quite matching 100% to what I expect from the NPM library.
My next problem is my authenticated.js policy appears to always fail.
My code is below:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.authenticated) { // <---- this is the error line
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
No Login Prompt Solution
sigh
I think I'm beginning to grasp some of the difference between SailsJS and pure ExpressJS codes.
The problem appears that I was missing this piece of code at the end of my passport.authenticate() method:
(req, res)
I picked it up after looking this tutorial again: http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/
So now, the final authenticate method should look like:
passport.authenticate('linkedin', function(err, user, info) {
// The request will be redirected to LinkedIn for authentication, so this
// function will not be called.
})(req, res); // <--- notice this extra (req, res) code here
Which matches the Passportjs documentation:
passport.authenticate('local'),
function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
res.redirect('/users/' + req.user.username);
});
In a way....if you know what I mean... :D
Now I got my Linkedin login prompt as expected.
Finally!
Callback Error Solution
OK.....I'm not sure if this is completes the login process...but....
I noticed I had an extra line:
passReqToCallback: true
Taken from this page here:
https://github.com/auth0/passport-linkedin-oauth2/issues/29
I removed that and I got a different error message.
I've also changed my callback code to look like:
passport.authenticate('linkedin', function(err, user, info) {
res.json(200, {
user: user
});
})(req, res);
and I got my user JSON which appears to be my Linkedin user profile info:
{
user: {
provider: "linkedin",
...
}
}
But that's...contradicting the Linkedin documentation...I don't see any access_token or expire_in properties which I was expecting to see in step 3 of the Linkedin OAuth 2.0 documentation (https://developer.linkedin.com/docs/oauth2)...
So...supposedly...I should take this user object and create/update against an existing user object ?
Authentication Always Fails Solution
OK, so few more days, I added extra code to generate a User entity if one isn't found in my database, otherwise just return the found user.
The was one last problem, in my policies folder, I have a authenticated.js and it looked like this:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.authenticated) { // <---- this is the error line
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
Being new to all this web development stuff, I thought:
req.authenticated; // should call match name of the file ?
was correct but I was following this tutorial:
http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/
and he named his file: isAuthenticated.js I figured it's just a name....but I was wrong :D
Turns out, the correct code was:
req.isAuthenticated()
So in full, the correct code becomes:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.isAuthenticated()) { // alright, that's more like it!
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
Perhaps isAuthenticated is a Passportjs function and not just a name like I initially thought.
My further research shows this page which suggests so to me:
Problems getting Passport.js to authenticate user
Maybe req.authenticated can only be used for HTML email-password login form as suggested in above Stackoverflow post and req.isAuthenticated() is for OAuth stuff.
Anyhow, I still don't know if this is the right path but so far, I got authentication in my application now and I can access protected resources. Not sure how long I'll be logged in for, maybe I still need to build the refresh token thingo every 15 minute like the Linkedin documentation stated ?
Hope this helps other fellow Sailsjs users who are facing the same problem :)
Does Linkedin expect my server to be running on HTTPS before it lets
this whole thing starts working ?
No. The API works just as well on a local http setup.
Is there some special configurations that I need to do in my Linkedin
developers app setting ? (I've enabled all the correct Javascript SDK
URLs)
No, your setup is fine.
The browser keeps loading because after the authentication LinkedIn redirects to your callback action which isn't handling the response stream.
You need to handle the response in the callback action. Something like this will do:
callback: function(req, res) {
passport.authenticate('linkedin', function(err, user){
// handle error
// do something with the user (register/login)
return res.redirect('/home');
});
}
I'd highly recommend using sails-generate-auth for maintaining third-party logins. Very easy to setup and configure. All you need to do is serve the access tokens and secrets for the different strategies (either through config/passport.js or, preferably, through config/local.js). Will spare you a lot of redundant code.

Node.js API - Allow users to only update and delete their own object

I am trying to build a RESTful API using Node.js w/ Express. I am fairly new to the MEAN stack, and want to use best practices. The concept I'm having trouble grasping and implementing is the following:
Restricting routes like PUT and DELETE on a user object, to only allow requests from users who 'own' this object.
One possibility I've thought of:
Creating secret token for users that matches token in DB
So when creating a user I assign them a token, store this in the DB and attach it to their session data.
Then my middleware would look something like:
router.put('/api/users/:user_id', function(req, res, next) {
// already unclear how this token should be transfered
var token = req.headers['x-access-token'] || req.session.token;
// update user (PUT /api/users/:user_id)
User.findById(req.params.user_id, function(err, user) {
if (err) {
res.send(err);
} else if (user.token != token) {
res.json({ sucess: false, message: 'User not same as authenticated user.' });
} else {
// set new information only if present in request
if (req.body.name) user.name = req.body.name;
if (req.body.username) user.username = req.body.username;
...
// save user
user.save(function(err) {
if (err) res.send(err);
// return message
res.json({ message: 'User updated.' });
});
}
});
Questions I have regarding best practice
Is the scenario I thought of at all plausible?
What data should I use to create a unique token for a user?
Is storing the token in the session the best solution?
Sidenote
This is a learning project for me, and I am aware of libraries like Passport.js. I want to learn the fundamentals first.
I have a repo for this project if you need to see some of the surrounding code I'm using: https://github.com/messerli90/node-api-ownership
Edit
I would accept a good RESTful API book recommendation, where these points are covered, as an answer.
Edit 2
I actually found a lot of the answers I was looking for in this tutorial: http://scottksmith.com/blog/2014/05/29/beer-locker-building-a-restful-api-with-node-passport/
I was trying to do this without the use of passport.js but a lot of the concepts covered in the article made some of the mechanics of an authorized API clear to me.
If I understand your question, this is an API, and the client (not a browser) is passing the secret token (api key) in the request, in a header. Seems reasonable. Of course, you must require https to protect the api key. And, you should have a way for users to revoke/regenerate their API key.
So far, I don't think you need to store anything in the session. It seems like storing the token in the session just complicates things. Presumably, if you are going to establish a session, the client has to include the token in the first request. So, why not just require it on each request and forget the session? I think this makes life simpler for the api client.
A 'bit' too late, but if someone is still looking for an answer, here is how i did it:
router.put('/', function(req, res) {
var token = req.headers['x-access-token'];
if (!token) return res.status(401).send({auth:false, message:'No token provided'});
jwt.verify (token, process.env.SECRET, function (err, decoded) {
if(err) return res.status(500).send({auth:false, message:'failed to auth token'});
User.findByIdAndUpdate({_id: decoded.user_id}, req.body, function(err, user) {
if (err)
res.send(err);
res.json({username: user.username, email: user.email});
});
});
});
Just pass the user id that is stored in the token to the mongoose function. This way the user who sent the request can only update or delete the model with his ID.
Reading material:
Implementing Access Control in Node.JS
Found this super clear article on how to allow users to only delete replies they own. Hope it helps.
What worked for me:
.delete(requireAuth, async (req, res, next) => {
const knexInstance = req.app.get("db");
const comment = await CommentsService.getById(knexInstance, req.params.id);
if (comment === undefined) {
return res.status(404).json({
error: {
message: `Comment doesn't exist.`
},
});
}
if (comment.users_id !== req.users.id) {
return res.status(401).json({
error: {
message: `You can only delete your own comments.`
},
});
}
CommentsService.deleteComment(knexInstance, req.params.id)
.then((numRowsAffected) => {
res.status(204).end();
})
.catch(next);
})

Resources