30
votes

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.

3
Have you found a solution to this? I have same problem.JasonY
My solution was not the best, but what I did was to redirect with a query param. I could have sent the JWT - but that would be unsafe (since query params are part of the url and saved in the browser's history, therefore can be abused easily). What I did instead was to redirect with the new user Id, and on that redirected page load, I sent a request to my server to receive the user's JWT using its id. In order to make it as secure as possible I added a timestamp to the user's object on the db, so that JWT can be retrieved within 30 seconds (assuming redirection would not take longer than that)Bar Kedem

3 Answers

16
votes

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.

4
votes

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 />;
  }
}
3
votes

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.