MERN OAuth 实施不会重定向到 google 登录页面

MERN OAuth Implementation does not redirect to the google login page

我目前 运行 是一个使用 MERN 堆栈的网络服务器,我正在尝试让 OAuth 登录正常工作。但是,当我单击 "login with google" 按钮时,反应加载主页(但 URL 发生变化)。获取 URL 直接从服务器得到 302 响应,但我的 front-end 没有改变。

Server.js

const express = require('express');
const path = require('path');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const logger = require('morgan');
const cors = require('cors');
const secure = require('express-force-https');
const passport = require('passport');
const cookieSession = require('cookie-session');

require('dotenv').config();

const app = express();
const port = process.env.PORT || 5000;
const dbRoute = process.env.MONGODB_URI || 'NO DB ROUTE PROVIDED';

// db setup
mongoose.connect(
  dbRoute,
  {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    dbName: process.env.DATABASE_NAME,
  }
);

let db = mongoose.connection;
db.once('open', () => console.log("Connected to the database"));
db.on('error', console.error.bind(console, "MongoDB connection error: "));

// middleware
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false })); // body parsing
app.use(bodyParser.json());
app.use(logger("dev"));
app.use(express.static(path.join(__dirname, "client", "build"))); // for serving up the clientside code
app.use(secure); // ensure that the connection is using https
app.use(cookieSession({ // cookies!
  maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
  keys:['vcxzkjvasddkvaosd'] // yeah i'm sure that's secure enough
}));

// models
require('./models/rule');
require('./models/affix');
require('./models/user');

// passport security
require('./config/passport');
app.use(passport.initialize());
app.use(passport.session());

// routes
app.use(require('./routes'));

// The "catchall" handler: for any request that doesn't
// match one above, send back React's index.html file.
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname+'/client/build/index.html'));
});

app.listen(port);

console.log(`Server listening on ${port}`);

路线(在不同的文件夹中有一些索引文件,所以这条路线的完整路径是/api/user/google

const mongoose = require('mongoose');
const passport = require('passport');
const router = require('express').Router();
const auth = require('../auth');
const User = mongoose.model('User');

router.get('/google', 
  passport.authenticate('google', {
    scope: ['profile', 'email']
  })
);

router.get('/google/callback', 
  passport.authenticate('google', { failureRedirect: '/affixes'}),
  (req, res) => {
    res.redirect('/?token=' + req.user.token);
  }
);

Passport.js

const mongoose = require('mongoose');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
require('dotenv').config();

const User = mongoose.model('User');

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id).then((user) => {
    done(null, user);
  })
});

passport.use(new GoogleStrategy({
    clientID: process.env.OAUTH_CLIENT_ID,
    clientSecret: process.env.OAUTH_CLIENT_SECRET,
    callbackURL: '/api/user/google/callback',
    proxy: true
  },

  (accessToken, refreshToken, profile, done) => {
    User.findOne({ googleId: profile.id })
      .then((existingUser) => {
        if (existingUser) {
          done(null, existingUser);
        } else {
          new User({ googleId: profile.id }).save()
            .then((user) => done(null, user));
        }
      });
  }
));

前端登录页面(有一个获取按钮和一个 link 按钮。如上所述,不同的行为)

import React from 'react';
import {
  ComingSoon
} from '../Common';
import {
  Button
} from '@material-ui/core';

const handleClick = () => {
  fetch('/api/user/google')
}

export default function Login() {
  return (
    <>
      <Button onClick={handleClick}>
        Login with Google
      </Button>

      <a href="/api/user/google"><button>Log in with Google</button></a>
    </>
  );
}

更新:看起来像是某种 CORS 问题,虽然我仍然不知道如何解决它。浏览器吐出

Access to fetch at '...' (redirected from 'http://localhost:3000/api/user/google') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

添加请求的 header 得到

Access to fetch at '...' (redirected from 'http://localhost:3000/api/user/google') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.

原来我对这个问题的性质完全错了!问题是我对 OAuth 端点的提取请求正在调用我的前端,而不是我的后端,因为该请求在其 Accept header 中包含 text/html。使用 React 高级代理设置路由到正确的 URI 解决了这个问题。

参见:https://github.com/facebook/create-react-app/issues/5103 and https://github.com/facebook/create-react-app/issues/8550