网站搜索

reactjs - 使用 React/Nodejs/Passport 进行 Google 身份验证/授权时的 CORS 问题


我遇到了与问题 CORs 错误相同的问题:从 React 到 Express 的 Google Oauth(PassportJs 验证)。但我无法让 @Yazmin 提供的解决方案起作用。

我正在尝试创建一个包含 Google 身份验证和授权的 React、Express/Nodejs、MongoDB 技术栈。我目前在 Windows 10 上开发这个技术栈,使用 Vs Code(React 在 ‘localhost:3000’,Nodejs 在 localhost:5000,MongoDB 在 localhost:27017)。

该应用的目的是使用 Google 地图、Google 照片 API 和 Google Gmail API 在地图上显示城市速写(图片)。将来我可能还需要类似访问 Facebook 群组以获取城市速写的权限。但目前我只包括了个人资料和电子邮件权限用于授权。

我想将所有对第三方资源的请求保留在后端,因为从架构上我理解这是最佳实践。

来自源 http://localhost:5000 的谷歌授权流程运行良好,并返回预期结果。然而,当我尝试从客户端 - 源 Http://localhost:3000 执行相同操作时,在开发者工具控制台中第一次尝试访问谷歌 auth2 API 后返回以下错误。虽然方案和域名相同,但端口不同,因此来自第三方 (Https://account.google.com) 的消息已被浏览器拒绝。

从来源 'http://localhost:3000' 访问 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email%20https%3A%2F%2Fmail.google.com%2F&client_id='(从 'http://localhost:3000/auth/google' 重定向)时被 CORS 策略阻止:请求的资源上没有 'Access-Control-Allow-Origin' 头。如果不透明响应可以满足你的需求,请将请求的模式设置为 'no-cors' 来在禁用 CORS 的情况下获取资源。

无论我怎么尝试,错误信息都是一样的。

我认为谷歌正在将回复发送给客户端(localhost:3000),而不是服务器。

在其他解决方案中,我尝试通过以下方式实现Yilmaz的解决方案,引用:“在 client/src 中创建 setupProxy.js 文件。不需要在任何地方导入它。create-react-app 会查找此目录。”我之前已经通过运行 create-react-app 创建了我的客户端。因此我在我的 src 文件夹中添加了 setupProxy.js。

问题:我假设我是正确的,即在我重启客户端后,包含我设置的新 setupProxy.cjs 文件将被 webpack 包含。

在我看来,我得到的流程不是 BROWSER ==> EXPRESS ==> GOOGLE-SERVER,而是 BROWSER ==> EXPRESS ==> GOOGLE-SERVER ==> BROWSER,在这里它如上所示停在了 CORS 错误。

为了测试这个理论,我在 client\node_modules\http-proxy-middleware\lib\index.js 文件的“shouldProxy”和“middleware”函数中放了一些控制台日志消息,但无法检测到来自 Google 授权服务器响应(https://accounts.google.com/o/oauth2/v2/auth)的 auth/google 端点的任何活动。

所以我认为我的理论是错的,我不知道我该如何让它工作。

从 React 客户端发起对 /auth/google 端点的请求后,在 VsCode 终端上显示的控制台日志消息如下...

http-proxy-middleware - 92 HttpProxyMiddleware - shouldProxy
  context [Function: context]
  req.url /auth/google
  req.originalUrl /auth/google
  Trace
      at shouldProxy (C:\Users\User\github\GiveMeHopev2\client\node_modules\http-proxy-middleware\lib\index.js:96:13)
      at middleware (C:\Users\User\github\GiveMeHopev2\client\node_modules\http-proxy-middleware\lib\index.js:49:9)
      at handle (C:\Users\User\github\GiveMeHopev2\client\node_modules\webpack-dev-server\lib\Server.js:322:18)
      at Layer.handle [as handle_request] (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\layer.js:95:5)
      at trim_prefix (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:317:13)
      at C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:284:7
      at Function.process_params (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:335:12)
      at next (C:\Users\User\github\GiveMeHopev2\client\node_modules\express\lib\router\index.js:275:10)
      at goNext (C:\Users\User\github\GiveMeHopev2\client\node_modules\webpack-dev-middleware\lib\middleware.js:28:16)
      at processRequest (C:\Users\User\github\GiveMeHopev2\client\node_modules\webpack-dev-middleware\lib\middleware.js:92:26)
http-proxy-middleware - 15 HttpProxyMiddleware - prepareProxyRequest
req localhost

The Google callback uri is http://localhost:5000/auth/google/callback

这是我的 Node.js 服务器代码的清单。

dotenv.config();
// express
const app = express();
// cors
app.use(cors())

// passport config
require ('./config/passport')(passport)

// logging
if( process.env.NODE_ENV! !== 'production') {
    app.use(morgan('dev'))
}
const conn = process.env.MONGODB_LOCAL_URL!

/**
 * dbConnection and http port initialisation
 */

const dbConnnect = async (conn: string, port: number) => {

    try {
        let connected = false;
        await mongoose.connect(conn, { useNewUrlParser: true, useUnifiedTopology: true })
        app.listen(port, () => console.log(`listening on port ${port}`))
        return connected;
    } catch (error) {
        console.log(error)
        exit(1)
    }
}

const port = process.env.SERVERPORT as unknown as number
dbConnnect(conn, port)
//index 02
// Pre Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }))


const mongoStoreOptions = {
    mongoUrl: conn,
    collectionName: 'sessions'
}

app.use(
    session({
        secret: process.env.SESSIONKEY as string,
        resave: false,
        saveUninitialized: false,
        store: MongoStore.create(mongoStoreOptions),
    })
)

app.use(passport.initialize())
app.use(passport.session())

// Authentication and Authorisation

const emailScope: string = process.env.GOOGLE_EMAIL_SCOPE as string
//GOOGLE_EMAIL_SCOPE=https://www.googleapis.com/auth/gmail/gmail.compose

const scopes = [
    'profile',
     emailScope

].join(" ")


app.get('/auth/google', passport.authenticate('google', {
    scope: scopes
}));

app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/'}),
    (req, res) => {
        res.send('Google Login Successful ')
    }
)

app.get('/', (req, res) => {
    res.send('Hello World');
})

http-proxy-middleware 的 setupProxy.cjs 文件。注意 cjs 扩展名。我猜这是因为我正在使用 Typescript。它在客户端的 src 文件夹中

const  createProxyMiddleware   = require('http-proxy-middleware');
module.exports = function (app) {
  app.use(createProxyMiddleware('/auth', {target: 'http://localhost:5000'}))
}

最后是来自客户端的 fetch 命令

async function http(request: RequestInfo): Promise<any> {
    try {
      const response = await fetch('/auth/google') 
      const body = await response.json();
      return body
    } catch (err) { console.log(`Err SignInGoogle`) }
  };

以及护照配置...

import { PassportStatic} from 'passport';
import {format, addDays} from 'date-fns'
import { IUserDB, IUserWithRefreshToken, ProfileWithJson} from '../interfaces/clientServer'

 
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const User = require('../models/User')



module.exports   = function (passport:PassportStatic) {

    const clientID: string = process.env.GOOGLE_CLIENTID as string
    const clientSecret: string = process.env.GOOGLE_SECRET as string
    const callbackURL: string = process.env.GOOGLE_AUTH_CALLBACK as string

    const strategy = new GoogleStrategy(
        {
            clientID: clientID,
            clientSecret: clientSecret,
            callbackURL: callbackURL,
            proxy: true
            
        },
        async (_accesstoken: string, _refreshtoken: string,
            profile: ProfileWithJson,
etc

答案#1

你不能对 /auth/google 路由发起 fetch 请求!
这是我在 JavaScript 中的解决方案...

// step 1:
// handler function should use window.open instead of fetch
const loginHandler = () => window.open("http://[server:port]/auth/google", "_self")

//step 2: 
// on the server's redirect route add this successRedirect object with correct url. 
// Remember! it's your clients root url!!! 
router.get(
    '/google/redirect', 
    passport.authenticate('google',{
        successRedirect: "[your CLIENT root url/ example: http://localhost:3000]"
    })
)

// step 3:
// create a new server route that will send back the user info when called after the authentication 
// is completed. you can use a custom authenticate middleware to make sure that user has indeed 
// been authenticated
router.get('/getUser',authenticated, (req, res)=> res.send(req.user))

// here is an example of a custom authenticate express middleware 
const authenticated = (req,res,next)=>{
    const customError = new Error('you are not logged in');
    customError.statusCode = 401;
    (!req.user) ? next(customError) : next()
}
// step 4: 
// on your client's app.js component make the axios or fetch call to get the user from the 
// route that you have just created. This bit could be done many different ways... your call.
const [user, setUser] = useState()
useEffect(() => {
    axios.get('http://[server:port]/getUser',{withCredentials : true})
    .then(response => response.data && setUser(response.data) )
},[])

说明....
步骤 1 会在浏览器中加载您的服务器认证 URL 并发出认证请求。
步骤 2 认证完成后,在浏览器中重新加载客户端 URL。
步骤 3 提供一个 API 端点以收集用户信息来更新 React 状态。
步骤 4 调用该端点,获取数据并更新用户状态。