Reactgo: Populate User State After Authentication

Created on 26 May 2017  路  13Comments  路  Source: reactGo/reactGo

What is the suggested method for loading user data (ex: email, fname) after a successful login?

Should it be fetched after POST, or should the controller pass an object back on successful passport.authenticate(...)?

Thx 馃憤

question

Most helpful comment

So it actually something "wrong" with the manual login (We should have payed more attention to it 馃槃 )
When you perform a manual login (using local strategy) it behaves a little different than what we've described before.

You send a request from the web page using Axios to the server with the user details to perform a login
You get to the login endpoint on the server. That checks the user details and if everything is correct, it returns a 200 status.
The action that you've previously sent gets a response back, and does dispatch(push('/dashboard'));. This triggers a CLIENT-SIDE redirect, meaning only the web site changes, it doesnt go to the backend for this. So you dont get the user details like you've done in middleware.js

When using a manual login, i see 3 options that can be done (We might actually want to change ReactGo logic as well for this)

  1. Force a page refresh - to get to the SSR render - by changing dispatch(push('/dashboard')); to window.location = '/dashboard
  2. Return the user data (Only what's needed) from the manual login endpoint back to the web page and trigger and dispatch action to update the store with the received data (Same for signup)
  3. After a successful login, send another request to get the user data.

I think the optimal choice for manual login is (2) because it wont cause a refresh of the page so the user will have a more smooth experience.
When using google login, however, its not possible to have something like this because the flow redirects you to Google site itself, so either way you're leaving the page.

All 13 comments

I would say that after a successful login passport.authenticate you should redirect to your home page.
When that happens, it would trigger an SSR in middleware.js where the creation of a new store would happen.
As you can see there, the user details are initialized so if req.isAuthenticated() returns true, you can grab the user details that you've saved on the session by using req.user and save them / or some of them in the store

@slavab89 Thanks for the tip, so far the data only appears after a hard-refresh. Can you show how you would redirect after passport.authenticate? What am I missing here?

I'm doing the following: (For some reason its not really documented in passport docs)

    app.get('/auth/callback',
      passport.authenticate('google', { failWithError: true }),
      (req, res) => {
        res.redirect('/');
      },
      (err, req, res, next) => { // Special specific error callback for that route
        res.redirect('/?loginError=' + encodeURIComponent(err));
      }
    );

But it should work with the current code that's in the branch.

And in middleware.js

  const store = configureStore({
    user: {
      authenticated,
      userData: req.user,
      ......
    },
    ......
  }, history);

It should trigger a SSR request because you force the user to do a redirect to your home page. You could add a print in middleware.js to see if it actually reaches there...

I populate them in deserialize function like below

export function deserializeUser (id, done) => {
  User.findById(id).populate('friendRequest friends').exec((err, user) => {
    done(err, user);
  });
};

You guys are awesome, really appreciate the help on this! Feels so close to working... However, still coming up empty handed on both options...

@slavab89 I'm only using 'local' passport login setup (google login is disabled). Using the latest on master, and that first part looks pretty unique. I get the middleware.js part, but the other part has me a little confused... Are you saying that instead of return res.sendStatus(200); on successful login to return res.redirect('/');?

@ZeroCho How would I modify the latest (master) deserializeUser() to populate user.profile state? Is there anything else that needs to be changed as well?

export default (id, done) => {
  User.findById(id, (err, user) => {
    done(err, user);
  });
};

$10 bounty on whoever nails the solution - Thx! 馃憤

In ReactGo case, you can use user.profile without doing anything.

@ZeroCho So, when a user logs in, the profile object of that DB-user isn't being populated into the user.profile state. That's my current problem/objective.

@slavab89 is onto something, because when you take in req.user.profile on configureStore() it WORKS... However, it doesn't appear or update the state until after you refresh the browser.

This is what I currently have:

middleware.js

const store = configureStore({
    user: {
      authenticated,
      isWaiting: false,
      message: '',
      refCode: authenticated ? req.user.refCode : '',
      profile: authenticated ? req.user.profile : {}
    }
  }, history);

db/mongo/controllers/users.js

export function login(req, res, next) {
  passport.authenticate('local', (authErr, user, info) => {
    if (authErr) return next(authErr);
    if (!user) {
      return res.sendStatus(401);
    }
    return req.logIn(user, (loginErr) => {
      if (loginErr) return res.sendStatus(401);
      return res.sendStatus(200);
    });
  })(req, res, next);
}
const UserSchema = new mongoose.Schema({
  email: { type: String, unique: true, lowercase: true },
  password: String,
  tokens: Array,
  profile: {
    name: { type: String, default: '' },
    gender: { type: String, default: '' },
    location: { type: String, default: '' },
    website: { type: String, default: '' },
    picture: { type: String, default: '' }
  },
  resetPasswordToken: String,
  resetPasswordExpires: Date,
  google: {}
});

Current user schema looks like this.
What about email, password, tokens? Do they come to user object? If so, there's no reason that profile object doesn't come to user.profile.

and @slavab89 's answer is correct. However, if it doesn't appear before refreshing the browser, it means that something's wrong in SSR.

If you're using mongo (which seems like you do 馃槃 ) Then ReactGo is configured to work with a mongo DB store.
The flow of passport is basically:

  1. Go to login route - Your local login route.
  2. Verify the use info here
  3. If the user is a match, serialize it. It will save the use ID in the DB.
  4. Handle success callback (redirect to home)
  5. A new request should come to the server. Before reaching middleware.js it should deserialize the user using the local passport config. It should fetch the user based on the ID that was saved in the session.
  6. Should get to middleware.js and create the store correctly with filled req.user object. (You can also see req.session for the full session data)

I hope that i'm correct with that flow 馃槣 .
Try adding a console.log print in both serializeUser and serializeUser to see what info you get there. Or console.log in another points to see that everything is corrected.

Please clone and run this: https://github.com/jrodl3r/reactGoUserDataPop. Here are the diff/changes.

After logging-in user.profile.refCode doesn't update until after a refresh for me... 馃槮

So it actually something "wrong" with the manual login (We should have payed more attention to it 馃槃 )
When you perform a manual login (using local strategy) it behaves a little different than what we've described before.

You send a request from the web page using Axios to the server with the user details to perform a login
You get to the login endpoint on the server. That checks the user details and if everything is correct, it returns a 200 status.
The action that you've previously sent gets a response back, and does dispatch(push('/dashboard'));. This triggers a CLIENT-SIDE redirect, meaning only the web site changes, it doesnt go to the backend for this. So you dont get the user details like you've done in middleware.js

When using a manual login, i see 3 options that can be done (We might actually want to change ReactGo logic as well for this)

  1. Force a page refresh - to get to the SSR render - by changing dispatch(push('/dashboard')); to window.location = '/dashboard
  2. Return the user data (Only what's needed) from the manual login endpoint back to the web page and trigger and dispatch action to update the store with the received data (Same for signup)
  3. After a successful login, send another request to get the user data.

I think the optimal choice for manual login is (2) because it wont cause a refresh of the page so the user will have a more smooth experience.
When using google login, however, its not possible to have something like this because the flow redirects you to Google site itself, so either way you're leaving the page.

Very interesting... Considering how much of the codebase is dedicated to the user, I'd think this functionality (simple user data population) and a basic UI on say /dashboard should be considered for addition to the project.

There is one caveat with option 2 in that if you refresh the page, guess what happens? User profile data goes bye-bye. However, this action in tandem w/ the store nails both scenarios 馃槈 馃憤

@slavab89 shoot me an email

Should we close this then?
btw, i've tried to write you and email (Based on your github profile) but seems like that address does not exist

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jrodl3r picture jrodl3r  路  7Comments

Cleanshooter picture Cleanshooter  路  7Comments

jrodl3r picture jrodl3r  路  6Comments

choonkending picture choonkending  路  8Comments

choonkending picture choonkending  路  4Comments