passport-facebook: Returning 500 error instead of redirecting to failureRedirect

I’m using the following code for my callback route.

app.get('/auth/facebook/callback', passport.authenticate('facebook', {
        successRedirect : '/profile',
        failureRedirect : '/',
        failureFlash : true
}));

It works, authentication works as expected, but one thing I just can’t seem to understand is why if I visit /auth/facebook/callback?code=gfdgsdgfg it throws a 500 error and displays a stacktrace to the user?

Is there no way to use the failureRedirect?

Is there a work around or anything that can be done so that if the authentication fails (for any reason, mainly because a user has tampered with the url) it redirects the user to the failureRedirect location and gives them a generic error response.

About this issue

  • Original URL
  • State: open
  • Created 10 years ago
  • Reactions: 3
  • Comments: 18

Most upvoted comments

The resolution is to pass a custom callback to passport.authenticate( ) and handle the error using the callback flow. The design of everything in Passport is based on the middleware approach so understand that means understanding a custom callback must be provided for anything other than the default handling.

Here is an example:

passport.use(new FacebookStrategy({
       clientID: settings.server.facebook.clientId,
       clientSecret: settings.server.facebook.clientSecret,
       callbackURL: settings.server.facebook.oAuthRedirect1,
       profileFields: ['displayName', 'email'],
       passReqToCallback: true
       }, 
    //done method is the passport.authenticate callback
    async (req, accessToken, refreshToken, profile, done) => { 
    try {
      var r = await myapi.authenticate(accessToken, profile); 
      if(!r.authorized) {
        done('unauthorized'); //error (calls the passport.authenticate callback)
      } else {
        done(null, { //no error (calls the passport.authenticate callback)
          token: r.token,
          fbid: profile.id,
          fb_access_token: accessToken,
          profile: profile
        });
      }
    }
    catch (e) {
      logger.error(e);
    }
    })
 );

This is the route that calls the above strategy and upon completion has the “done” handler defined which can provide custom logging, ensure the redirect occurs, whatever.

   router.get('/login/facebook/return',
   (req, res, next) => {
   return passport.authenticate('facebook', {
          failureRedirect: '/login',
          session: false
          },
       (err, user, info) => {
          if(err) {
            logger.error(err);
            res.redirect('/login');
          }
          else
          {
              next();
          }
      })(req, res, next);
  })

No matter what error you get from the facebook strategy (including invalid profile fields, invalid code, etc) will all result in logging it and redirecting to /login in the above coded scenario

You can handle it in an express error handler:

import * as TokenError from 'passport-oauth2/lib/errors/tokenerror';
// ...
app.use((error, req, res, next) => {
        if (error instanceof HttpError) {
            res.status(error.httpCode).json({
                name: error.constructor.name,
                ...omit(error, 'name', 'stack', 'httpCode')
            });
        } else if (error instanceof TokenError) { // this is 
            res.redirect('/login/');
        } else {
            console.error(error);
            res.status(500).json({});
        }
    });

Strangely having pretty much the same experience as @jwerre except using the Google module.

If I go to http://canvass.prototype.website/auth/google/callback?code=flubber

Error
    at Strategy.OAuth2Strategy.parseErrorResponse (/var/lib/openshift/587529732d52711f6d000124/app-root/runtime/repo/node_modules/passport-oauth2/lib/strategy.js:329:12)
    at Strategy.OAuth2Strategy._createOAuthError (/var/lib/openshift/587529732d52711f6d000124/app-root/runtime/repo/node_modules/passport-oauth2/lib/strategy.js:376:16)
    at /var/lib/openshift/587529732d52711f6d000124/app-root/runtime/repo/node_modules/passport-oauth2/lib/strategy.js:166:45
    at /var/lib/openshift/587529732d52711f6d000124/app-root/runtime/repo/node_modules/oauth/lib/oauth2.js:191:18
    at passBackControl (/var/lib/openshift/587529732d52711f6d000124/app-root/runtime/repo/node_modules/oauth/lib/oauth2.js:132:9)
    at IncomingMessage.<anonymous> (/var/lib/openshift/587529732d52711f6d000124/app-root/runtime/repo/node_modules/oauth/lib/oauth2.js:157:7)
    at emitNone (events.js:91:20)
    at IncomingMessage.emit (events.js:185:7)
    at endReadableNT (_stream_readable.js:974:12)
    at _combinedTickCallback (internal/process/next_tick.js:74:11)

I’m having a similar issue that may be related though my setup is slightly different. The issue is that errors are not passed back to the authenticate callback so I’m not able to handle them.

router.get("/authorize/facebook/callback", function(req, res, next) {
	
	passport.authenticate("facebook", function(err, profile, options) {

		if (err) {
			return res.redirect("/500");
		}
		if (!profile) {
			return res.redirect("/404");
		}

	})(req, res, next);

});

Then, if I go to /authorize/facebook/callback?code=flubber my redirects are never reached and the following error is thrown:

error: {"name":"FacebookTokenError","message":"Invalid verification code format.","type":"OAuthException","code":100,"traceID":"FXjfQTrvjGS","status":500}
stack: FacebookTokenError: Invalid verification code format.
	at Strategy.parseErrorResponse (/node_modules/passport-facebook/lib/strategy.js:196:12)
	at Strategy.OAuth2Strategy._createOAuthError (/node_modules/passport-oauth2/lib/strategy.js:376:16)
	at /node_modules/passport-oauth2/lib/strategy.js:166:45
	at /node_modules/oauth/lib/oauth2.js:177:18
	at passBackControl (/node_modules/oauth/lib/oauth2.js:123:9)
	at IncomingMessage.<anonymous> (/node_modules/oauth/lib/oauth2.js:143:7)
	at emitNone (events.js:91:20)
	at IncomingMessage.emit (events.js:185:7)
	at endReadableNT (_stream_readable.js:974:12)
	at _combinedTickCallback (internal/process/next_tick.js:74:11)
	at process._tickDomainCallback (internal/process/next_tick.js:122:9)

I tried the workaround suggested by @matthewerwin and I note a couple of things:

  1. if you are using a session, then in the success state in the /login/facebook/return route you have to call req.logIn(user, err => {...dosomething... }). From what I can tell from sparse documentation when you take over the authenticate function you have to do all the steps yourself. the next() in the above code doesn’t appear to do anything. Also the options for successRedirect and failureRedirect appear to be inoperative as well.

  2. I note that there should be a check for the user as well, if the user is not there an appropriate step should be taken (e.g. res.redirect(/some_error_url).

  3. I am unable to get the error message unauthorized passed into the callback handler using passport-mocked. Unfortunate, as I’d like to pass the message back to the client. (I’m attempting implement account suspension for facebook users… I’d like to tell them suspended in the response text). passport-mocked has a bug

The issue I had was that I was throwing an err if the user didn’t exist – done(new Error("Invalid username"), null); and that doesn’t seem to be what Passport expects.

It supports the notion of a falsy user in done as another sign of failure. So the appropriate signature would be done(null, null) if you don’t find a user.

That got things working for me.

You should not see the stack trace when you put the application in production mode (assuming you are using express), but it will still throw you a 500 error.

But 500 is wrong status code. It is not an internal server error but the user providing an invalid code. So probably a 400 or 404 status code would be correct.

I think this should be fixed in Passport, but you can easily catch this error your self and respond with the correct response.