4

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!

7
  • Did you ever get an answer for this I have the exact same problem at the moment Commented May 22, 2018 at 23:44
  • 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" ) Commented May 22, 2018 at 23:48
  • @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 file Commented May 23, 2018 at 15:44
  • @user934902, how do I reproduce the issue? I browse to "localhost:6555/api/auth/google" and it responses with a token Commented May 23, 2018 at 17:21
  • 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 API Commented May 23, 2018 at 17:53

1 Answer 1

3
+150

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.