It is my first experience with passport-jwt. I configured the strategy and all works fine. I send token to client and extract it from the header with fromAuthHeaderAsBearerToken() method. But I got one issue, I want to render one specific page in case if Authorization header is empty or it doesn't set. How can I do it, except set up failureRedirect: '/specific-page' property?
The code of strategy is pretty standard:
JwtStrategy(jwtOptions, (payload, done) => {
User.findOne({fmn_id: payload.data.id})
.then( (obj) => {
if(!obj) throw new Error('No user with such id');
done(null, obj);
})
.catch( (err) => {
res.render('specificPage'); //In case of error I want to render this page. Current code doesn't work.
done(null, err);
});
}
You can render a specific template using res.render('path_to_template')
Suppose you have a template in your views directory called myfile.ejs then you can render to this by res.render('myfile')
Or you can also redirect user to another route using res.redirect('/route');
Related
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.
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.
I have a sails.js app that generates API to my client. In order to secure my API I need to implement OAuth2.0 to my sails app. I have started to follow this tutorial: https://www.npmjs.com/package/sails-generate-auth#requirements
But I get all kinds of diffrent errors when every time when I try to lift the server. I also dont understand to where i'm suppose to send my credentials to the server and get the access token. I'm fairly new to Sails.js and just got to know OAuth and I can't find a proper guide on how to implement OAuth.
How can I implement OAuth to my app? please have a detailed answer so that I can fully understand.
UPDATE:
ok so instead I started to follow this guide: https://www.bearfruit.org/2014/07/21/tutorial-easy-authentication-for-sails-js-apps/
and I think I got everything to work as it should(?) when I register an account it saves the data in the database as it should. The login also seems to work properly But I didn't understood how I can access the actuall data like the username and email address after the login redirects me to the homepage? I've tested the login on postman and when i log in I get a cookie. What am I suppose to do with it?
The AuthController generated by sails-generate-auth doesn't add the user details to the session by default so you should add it manually by adding the following line to the callback function in AuthController.js
req.session.user = user;
This is how the callback looks like with the line:
callback: function (req, res) {
function tryAgain (err) {
// Only certain error messages are returned via req.flash('error', someError)
// because we shouldn't expose internal authorization errors to the user.
// We do return a generic error and the original request body.
var flashError = req.flash('error')[0];
if (err && !flashError ) {
req.flash('error', 'Error.Passport.Generic');
} else if (flashError) {
req.flash('error', flashError);
}
req.flash('form', req.body);
// If an error was thrown, redirect the user to the
// login, register or disconnect action initiator view.
// These views should take care of rendering the error messages.
var action = req.param('action');
switch (action) {
case 'register':
res.redirect('/register');
break;
case 'disconnect':
res.redirect('back');
break;
default:
res.redirect('/login');
}
}
passport.callback(req, res, function (err, user, challenges, statuses) {
if (err || !user) {
return tryAgain(challenges);
}
req.login(user, function (err) {
if (err) {
return tryAgain(err);
}
// Mark the session as authenticated to work with default Sails sessionAuth.js policy
req.session.authenticated = true;
req.session.user = user;
// Upon successful login, send the user to the homepage were req.user
// will be available.
res.redirect('/');
});
});
}
You can now use the user details in any of your controllers and views by referring to req.session.user for example twitter provides your user name so you can use req.session.user.username.
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);
})
I've successfully implemented passport-local into my Express/Mongoose web-app but I'm having trouble figuring out how to render a failed login message properly.
Here's my login route:
app.get('/login', function(req, res) {
res.render('user/login', {
});
});
With a route like that how am I supposed to report an invalid login? If the login is successful it will write the id/username to the req.user object but that doesn't help me in the "GET /login" route because if it's successful you will get redirected to the page you want to go.
That means req.user will always be undefined when you GET the login page.
I want to be able to write out a message saying something like 'yo, invalid login!' when the following things happen:
The user does not exist.
The password supplied does not match but the user existed.
I might want to output a different message depending on what occurred.
When I implemented the LocalStrategy I used this code:
passport.use(new LocalStrategy({
usernameField: 'email'
},
function(email, password, fn) {
User.findOne({'login.email': email}, function(err, user) {
// Error was thrown.
if (err) {
return fn(err);
}
// User does not exist.
if (!user) {
return fn(null, false);
}
// Passwords do not match.
if (user.login.password != utility.encryptString(user.login.salt + password)) {
return fn(null, false);
}
// Everything is good.
return fn(null, user);
});
}
));
As you can see there are some problems but this is how the author of PassportJS set up his application. How are we supposed to access what the Strategy returns?
Like if it throws an error, what am I supposed to even call to get access to err?
Thanks.
In the latest version of Passport, I've added support for flash messages, which make it easy to do what you are asking.
You can now supply a third argument to done, which can include a failure message. For example:
if (user.login.password != utility.encryptString(user.login.salt + password)) {
return fn(null, false, { message: 'yo, invalid login!' });
}
Then, set failureFlash to true as an option to authenticate().
passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login',
failureFlash: true });
In this case, if authentication fails, the message will be set in the flash, ready for you to render it when you display the login page again.
Custom callbacks are also perfectly fine. The built in options just make it simpler to accomplish common tasks.
Also, I'm curious: you mention that there are problems with the sample. What do you think should be improved? I want to make the examples as good as possible. Thanks!
(For more details, see this comment on issue #12).
You can use the custom callback or middleware functionality to have more control. See the Authentication section of the guide for examples.
For example, a custom callback might look like:
app.get('/login', function(req,res,next) {
passport.authenticate('local', function(err,user) {
if(!user) res.send('Sorry, you\'re not logged in correctly.');
if(user) res.send('Skeet skeet!');
})(req,res,next);
});
Alternatively, you could always redirect both responses:
app.get('/login',
passport.authenticate('local', { successRedirect: '/winner',
failureRedirect:'/loser' }));
Or redirect the failure with simple middleware:
app.get('/login', ensureAuthenticated,
function(req,res) {
// successful auth
// do something for-the-win
}
);
// reusable middleware
function ensureAuthenticated(req,res,next) {
if(req.isAuthenticated()) {return next();}
res.redirect('/login/again'); // if failed...
}