4
votes

Hi all I have been banging my head researching this for past 2 days with no luck.

This is the error I'm getting when trying to auth with Google Oauth2 Passport strategy from my React app @localhost:3000. I am running a separate app with a node/express server on localhost:3001.

XMLHttpRequest cannot load http:localhost:3001/api/auth/google/login. Redirect from 'http:localhost:3001/api/auth/google/login' to 'https:accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=https%3A%2F%2Fmail.google.com&client_id=***.apps.googleusercontent.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http:localhost:3000' is therefore not allowed access.

createError.js:16 

Uncaught (in promise) Error: Network Error at createError (createError.js:16) at XMLHttpRequest.handleError (xhr.js:87)

This is the code Im using in my client to try and login from one of my components:

// BUTTON

<div>
   <button className="btn btn-danger" onClick={props.googleAuth}>
      Login With Google
   </button>
</div>

// STATEFUL COMPONENT METHOD

googleAuth() {
    axios.get("http:localhost:3001/api/auth/google/login").then(res => {
      console.log("GOOGLE OAUTH 2 RES", res);
    });
  }

// CONTROLLER

passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: "/api/auth/google/callback",
      accessType: "offline"
    },
    (accessToken, refreshToken, profile, done) => {
      const info = {
        googleUsername: profile.displayName,
        googleAccessToken: accessToken,
        googleRefreshToken: refreshToken,
        googleEmail: profile.emails[0].value
      };

      User.findOrCreate({
        where: {
          googleId: profile.id
        },
        defaults: info
      })
        .spread(user => {
          if (user) return done(null, user.toAuthJSON); 
          // this is method that returns a signed JWT off user DB instance. 
          return done(null, false);
        })
        .catch(done);
    }
  )
);

// GOOGLE LOGIN

router.get(
  "/login",
  passport.authenticate("google", {
    scope: [
      "https://mail.google.com/",
      "https://www.google.com/m8/feeds/",
      "email",
      "profile"
    ]
  })
);

// GOOGLE CALLBACK
router.get(
  "/callback",
  passport.authenticate("google", {
    session: false,
  }), (req, res) => {
    res.json(req.user.token)
  }
);

Steps I've already taken to try and solve:

  • I disabled CORS on my browser
  • I tried the cors npm module on my routes / api on server side Nothing seems to work.
  • And a lot of other tinkering and desperation...

  • Based on the error, Google is preventing me from making a downstream request from my server and throwing the error (I think)...

What my goals are:

  • I want Google to return a user object, which I then store on in my DB (already coded logic for this)
  • Instead of having the server res.redirect() I want to res.json() a signed JWT (which I have properly wired up already).
  • I do not want to use session based auth on my server and keep things clean and stateless.

Is this even possible?It should also be noted that I have a dev environment setup:

Server startup with Concurrently (starts client and nodemon server concurrently - client@localhost:3000 makes proxy requests to server@localhost:3001) - not sure if this might be causing any problems?

Any help would be greatly appreciated!

1
Did you ever get an answer for this I have the exact same problem at the momentuser934902
URL in axios call seems to be wrong "http:localhost:3001/api/auth/google/login" can you format it properly and try again? (e.g. should be "http://localhost:3001/api/auth/google/login" )vittore
@TarunLalwani I uploaded a git repo here https://github.com/j-mcfarlane/Reactjs-SocialAuth-RESTAPI . Roadblock is inside the client folder inside components/common/Google.js fileuser934902
@user934902, how do I reproduce the issue? I browse to "localhost:6555/api/auth/google" and it responses with a tokenTarun Lalwani
Yes the API is working correct. It is the connection with React that is the issue. When you load up the client and visit localhost:3000 (i believe) and click the 'Log in with Google' buttton that is the issue. Cant return the token to the UI using an axios request to the APIuser934902

1 Answers

3
votes

So I was able to solve the issue. The issue is you are using passport.js for social auth, when you could have simple used something like

https://www.npmjs.com/package/react-social-login

But I would anyways tell how to get your current project working.

https://github.com/j-mcfarlane/Reactjs-SocialAuth-RESTAPI

You need to have the redirect to the UI and not api. So your config will change like below

googleCallbackURL: 'http://localhost:3000/api/auth/google/callback'

Next in your callback, instead of returning data, you will return a redirect like below

jwt.sign(payload, secret, { expiresIn: 220000 }, (err, token) => {
    if (err) return console.log(err)

    res.redirect("http://localhost:3000/login?token=" + token);
})

I have hardcoded to localhost:3000 but you should get the host from request and then use it.

Next you will update your App.js to add another route for /login, which will call a component SocialLogin

import React, { Component, Fragment } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'

import PrivateRoute from '../util/PrivateRoute'

// Components
import Navbar from './layout/Navbar'

import Landing from './Landing'
import SocialLogin from './SocialLogin'
import Dashboard from './Dashboard'

class App extends Component {
    render() {
        return (
            <Router>
                <Fragment>
                    <Navbar /><br />

                    <Route exact path="/" component={Landing} />
                    <Route exact path="/login" component={SocialLogin} />

                    <Switch>
                        <PrivateRoute path="/dashboard" component={Dashboard} />
                    </Switch>
                </Fragment>
            </Router>

        )
    }
}

export default App

The simplest SocialLogin I wrote is below

import React from 'react'
import qs from 'query-string'

const SocialLogin = (props) => {
    console.log(props.location.search)
    let params = qs.parse(props.location.search)
    window.localStorage.setItem('token', params['token'])
    return (<div>Landing Page - {params.token} </div>)
}

export default SocialLogin

Here you have the token and you can continue the flow the way you like it. Also in Google.js I changed below

<button onClick={this.googleAuth}>Signin With Google</button>

back to your commented

<a href="/api/auth/google">Log in with google</a>

You need redirects to work and you shouldn't AJAX your google auth call, else how will use select an account?

Now after all these fixes, I am able to get the token as shown below

Token on UI