How to redirect to correct client route after social auth with Passport (react, react-router, express, passport)

后端 未结 2 1259
無奈伤痛
無奈伤痛 2021-02-02 03:29

I have a React/Redux/React Router front end, Node/Express back end. I’m using Passport (various strategies including Facebook, Google and Github) for authentication.

2条回答
  •  一生所求
    2021-02-02 03:59

    In case anybody else is struggling with this, this is what I ended up going with:

    1. When user tries to access protected route, redirect to /login with React-Router.

    First define a component:

    // App.jsx
    
    const PrivateRoute = ({ component: Component, loggedIn, ...rest }) => {
      return (
        
            loggedIn === true ? (
              
            ) : (
              
            )
          }
        />
      );
    };
    

    Then pass the loggedIn property to the route:

    // App.jsx
    
    
    

    2. In /login component, save previous route to localStorage so I can later redirect back there after authentication:

    // Login.jsx
    
      componentDidMount() {
       const { from } = this.props.location.state || { from: { pathname: "/" } };
       const pathname = from.pathname;
       window.localStorage.setItem("redirectUrl", pathname);
    }
    

    3. In SocialAuth callback, redirect to profile page on client, adding userId and token as route params

    // auth.ctrl.js
    
    exports.socialAuthCallback = (req, res) => {
      if (req.user.err) {
        res.status(401).json({
            success: false,
            message: `social auth failed: ${req.user.err}`,
            error: req.user.err
        })
      } else {
        if (req.user) {
          const user = req.user._doc;
          const userInfo = helpers.setUserInfo(user);
          const token = helpers.generateToken(userInfo);
          return res.redirect(`${CLIENT_URL}/user/${userObj._doc._id}/${token}`);
        } else {
          return res.redirect('/login');
        }
      }
    };
    

    4. In the Profile component on the client, pull the userId and token out of the route params, immediately remove them using window.location.replaceState, and save them to localStorage. Then check for a redirectUrl in localStorage. If it exists, redirect and then clear the value

    // Profile.jsx
    
      componentWillMount() {
        let userId, token, authCallback;
        if (this.props.match.params.id) {
          userId = this.props.match.params.id;
          token = this.props.match.params.token;
          authCallback = true;
    
          // if logged in for first time through social auth,
          // need to save userId & token to local storage
          window.localStorage.setItem("userId", JSON.stringify(userId));
          window.localStorage.setItem("authToken", JSON.stringify(token));
          this.props.actions.setLoggedIn();
          this.props.actions.setSpinner("hide");
    
          // remove id & token from route params after saving to local storage
          window.history.replaceState(null, null, `${window.location.origin}/user`);
        } else {
          console.log("user id not in route params");
    
          // if userId is not in route params
          // look in redux store or local storage
          userId =
            this.props.profile.user._id ||
            JSON.parse(window.localStorage.getItem("userId"));
          if (window.localStorage.getItem("authToken")) {
            token = window.localStorage.getItem("authToken");
          } else {
            token = this.props.appState.authToken;
          }
        }
    
        // retrieve user profile & save to app state
        this.props.api.getProfile(token, userId).then(result => {
          if (result.type === "GET_PROFILE_SUCCESS") {
            this.props.actions.setLoggedIn();
            if (authCallback) {
              // if landing on profile page after social auth callback,
              // check for redirect url in local storage
              const redirect = window.localStorage.getItem("redirectUrl");
              if (redirect) {
                // redirect to originally requested page and then clear value
                // from local storage
                this.props.history.push(redirect);
                window.localStorage.setItem("redirectUrl", null);
              }
            }
          }
        });
      }
    

    This blog post was helpful in figuring things out. The #4 (recommended) solution in the linked post is much simpler and would probably work fine in production, but I couldn't get it to work in development where the server and client have different base URLs, because a value set to localStorage by a page rendered at the server URL will not exist in local Storage for the client URL

提交回复
热议问题