As per the documentation, if I was handling authentication requests like so, I would be able to capture successful attempts.
app.post('/login',
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);
});
But, like the documentation says:
By default, if authentication fails, Passport will respond with a 401 Unauthorized status, and any additional route handlers will not be invoked. If authentication succeeds, the next handler will be invoked and the req.user property will be set to the authenticated user.
How can I handle the unauthorized login attempt?
I know I can handle it with custom middleware but is there a better way?
You should have a look at the Custom Callback section in passport docs which explains about how to override the built in behavior of handling an authentication request. You can write a custom callback which will serve as the done function that you are invoking from the Strategy.
app.get('/login', function(req, res, next) {
/* look at the 2nd parameter to the below call */
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
Look at the second parameter to the passport.authenticate call, which will serve as the done function that you invoke from the local strategy.
See the done function invoked in the code below, which is the local strategy that you define for passport. You can call the done function with various available parameters like err, user, info set from the strategy according to the response from API call or db operation. These parameters will be processed by the above function definition in the passport.authenticate call.
passport.use(new LocalStrategy(
function(username, password, done) {
/* see done being invoked with different paramters
according to different situations */
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.verifyPassword(password)) { return done(null, false); }
return done(null, user);
});
}
));
In my case I didn't want the 401 error (which is the "default behavior"),
and I just wanted a simple redirect if the user wasn't authorized. The simple { failureRedirect: '/login' } worked for me, as explained on the PassportJS documentation (in the "Overview" and the "Redirects" header)
Despite the complexities involved in authentication, code does not
have to be complicated.
app.post('/login', passport.authenticate('local', { successRedirect:'/',
failureRedirect: '/login' }));
In this case, the redirect options override the default behavior. Upon
successful authentication, the user will be redirected to the home
page. If authentication fails, the user will be redirected back to the
login page for another attempt.
The accepted answer gives you more flexibility. But this answer is a bit simpler.
I'm pretty sure "failure" includes the case where there would be no user: if (!user), but I'm not sure if failure includes the error case or not: if (err), as seen in the accepted answer.
Related
I have just completed my signup auth with passport.js but I kept on getting error when I was trying to use the login auth
Error: Failed to serialize user into session
This was my post route :
router.post("/login",passport.authenticate('local-login'),function(req,res){
res.redirect("/users")
if (req.user) {
console.log("Logged In!")
} else {
console.log("Not logged in!")
}
})
I saw a comment on stackoverflow that says we need to do:
app.post('/login', passport.authenticate('local', {
successRedirect: '/accessed',
failureRedirect: '/access',
session: false
}));
In the login route.
Using the code above does solve the error message.Maybe this is my poor understanding of passport authentication but isn't the point of going through the login to store the user info in the session. If we set session to false how do we store the user info?
This is taken from the docs of passport.js.
Disable Sessions
After successful authentication, Passport will establish a persistent
login session. This is useful for the common scenario of users
accessing a web application via a browser. However, in some cases,
session support is not necessary. For example, API servers typically
require credentials to be supplied with each request. When this is the
case, session support can be safely disabled by setting the session
option to false.
So basically, the difference is that for clients such as browsers, you usually want to have session persistence. For cases when you're calling internal APIs and don't really need persistence, you disable sessions.
Try Below :
Please make sure you use newest version of passport (which is 0.2.1 for today).
Please try passing { session: false } as a second parameter of your req.logIn() function:
app.get('/login', function (req, res, next) {
passport.authenticate('local', function (err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, { session: false }, function (err) {
// Should not cause any errors
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
Open this below link for more :
PassportJS - Custom Callback and set Session to false
passport has been a real thorn in my side lately. I'm trying to register a user and have a custom callback. However, if the user doesn't supply a username and password and just submits the form I get back an 'invalid credentials' error. I would like to intercept this before then so I can format it like the rest of my error messages, and send it back.
Is there any way to do this? I dug through passport and couldn't find anything.
You can make that happen on the callback, look at this example (not pretty code but it illustrates the idea well). In this case, if the user credentials are valid but the user has not confirmed their email address, I am returning a flash object to notify the user of the error. You could also make user of the /success or /failure options on passport to call specific url's upon a success or failure of authentication.
app.post('/login', function(req, res, next){
passport.authenticate('local', function(error, user, info){
if(error){
//handle error here
}
else{
if(!user){
res.status(404).end();
} else{
req.logIn(user, function(error){
if(error) return res.status(500).end();
if(!user.isEmailConfirmed){
req.session.flash = {
type: 'warning',
intro: 'Achtung',
message: 'error message'
};
res.redirect('/');
}
else res.redirect('/');
});
}
}
})(req, res, next);
});
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'm trying to implement a login/sign-up system similar to StackOverflow's, which is to say:
Both the sign up and login links go to /users/login,
Users click on an OAuth provider (e.g. Google) regardless of whether they're signing up or logging in,
The OAuth callback goes to /users/authenticate if the account doesn't yet exist (page to confirm account creation), OR goes to / if the account already exists.
(I'm adding an administrator account verification step here if the account is new, but not too important for this question.)
I'm not sure I'm going about this correctly. Relevant code below.
See if the profile exists in the database; if not, return the in-memory profile with the status = "new":
passport.use(new GoogleStrategy({
clientID: config.google_client_id,
clientSecret: config.google_client_secret,
callbackURL: "/auth/google/callback"
},
function (accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
db.db.collection("users", function (err, collection) {
if (err) throw err;
collection.findOne({id: profile.id}, function (err, record) {
if (record) return done(null, record);
profile.status = "new";
done(null, profile);
});
});
});
})
);
Pick the redirect route after OAuth based on status:
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/users/login' }),
function (req, res) {
switch (req.user.status) {
case "validated":
res.redirect('/'); break;
case "new":
res.redirect('/users/oauthconfirm'); break;
case "pending":
res.redirect('/users/login'); break;
}
}
);
And finally, the route for confirming a new account:
// app.js
app.get('/users/oauthconfirm', routes.users.oauthconfirm);
// routes/users.js
exports.oauthconfirm = function(req, res) {
db.db.collection("users", function (err, collection) {
if (err) throw err;
collection.insert(req.user, function (err, records) {
if (err) throw err;
res.render('login', {messages: [{status: "success", text:"Thank you. You will receive an e-mail when your account is validated."}]});
});
});
};
What's the "correct" way to do this? I'm pretty sure my verify callback code is inappropriate. Thanks-
How to OpenId with passport-google: OpenId configure strategy
How to Oauth with passport-google-oauth: OAuth configure strategy
By reading your code I can't actually tell which one you are trying to apply. Seems OAuth but then I see a route with /users/openidconfirm which I find unnecesary.
I'll share you 2 links I found:
OpenID vs. OAuth
What's the difference between OpenID and OAuth?
Maybe you could improve your question/trouble so I could elaborate a better answser.
Hope this helps though.
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...
}