【问题标题】:CORs Error: Google Oauth from React to Express (PassportJs validation)CORs 错误:从 React 到 Express 的 Google Oauth(PassportJs 验证)
【发布时间】:2020-03-21 00:18:51
【问题描述】:

我正在尝试使用 Google OAuth 身份验证设置 React/Redux - NodeJs Express 堆栈。我的问题是控制台中出现的 COR 错误。我发现了一些我认为正是我的问题的 Stack Overflow 问题,但这些解决方案没有产生任何结果。特别是这两个:CORS with google oauthCORS/CORB issue with React/Node/Express and google OAuth

所以我尝试了各种修复方法,但似乎都让我回到了同样的错误。这是其中最直接的:

const corsOptions = {
    origin: 'http://localhost:3000',
    optionsSuccessStatus: 200,
    credentials: true
}
app.use(cors(corsOptions));

这是在我的API.js 文件的根目录中。我收到的控制台错误状态:

从源“null”访问“https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fapi%2Foauth%2Fgoogle%2Freturn&scope=profile&client_id=PRIVATE_CLIENT_ID.apps.googleusercontent.com”处的 XMLHttpRequest(从“http://localhost:5000/api/oauth/google”重定向)已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:没有“访问控制” -Allow-Origin' 标头存在于请求的资源上。

因此,如果我在开发工具中查看我的网络日志,我会查看我对 API 路径的请求并查看我期望看到的内容:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Origin: http://localhost:3000

所以在我看来,我的问题不在于我的前后沟通。这让我相信这可能是 Passport 令牌验证的问题。这是我的简化路线:

router.post('/oauth/google', passport.authenticate('googleVerification', {
    scope: ['profile']
}), (req, res) => {
    console.log('Passport has verified the oauth token...');
    res.status(200)
});

以及回调路由:

router.get('/oauth/google/return', (req, res) => {
    console.log('google oauth return has been reached...')
    res.status(200)
});

最后,简化的策略:

passport.use('googleVerification', new GoogleStrategy({
    clientID: process.env.OAUTH_CLIENT_ID,
    clientSecret: process.env.OAUTH_SECRET,
    callbackURL: 'http://localhost:5000/api/oauth/google/return'
}, (accessToken, refreshToken, profile, cb) => {
    console.log('Passport OAuth Strategy reached');
    cb(null, profile)
}));

我知道所有这些都不会导致任何功能,但我只是尽可能多地扯掉了绒毛,试图弄清楚我的身份验证流程中的块在哪里。以防万一它有助于缩小范围,这里是 Redux 中的操作创建者,它在错误开始之前记录流程中的最后一步('redux 接受令牌并传递给 API:',令牌):

export const signIn = (token) => {
    console.log('redux accepting token and passing to API:', token)
    return async dispatch => {
        const res = await Axios({
            method: 'post',
            url: `${API_ROOT}/api/oauth/google`,
            withCredentials: true,
            data: {
                access_token: token
            }
        })

        console.log('API has returned a response to redux:', res)

        dispatch({
            type: SIGN_IN,
            payload: res
        })
    }
};

这实际上从未到达返回,并且不会记录第二个console.log

【问题讨论】:

  • 问题中引用的错误消息表明您的前端 JavaScript 代码正在向 https://accounts.google.com/o/oauth2/v2/auth 端点发出请求,并直接从该端点获得响应。但是由于该端点故意不包含 Access-Control-Allow-Origin 响应标头,因此您的浏览器会阻止您的前端代码访问响应。该端点并不意味着从代码中调用。相反,用户应该被导航到那里,然后重定向回您的应用程序。见stackoverflow.com/a/43276710等。
  • 根据问题中的代码 sn-ps ,我猜您可能正在尝试通过后端代理请求。但如果是这样,那不是正在发生的事情。相反,来自您的前端代码的请求似乎只是被重定向(未代理)到https://accounts.google.com/o/oauth2/v2/auth URL。无论如何,该 URL 唯一应该发生的事情是用户被导航到它,手动登录,然后将他们重定向回您的应用程序。
  • @sideshowbarker 非常感谢您的回复。这就是我怀疑的问题,但是尽管搜索了几天,我还是找不到关于流程应该如何工作的明确解释。在我看来,如果您想使用 oauth 进行验证和受保护的路由,则必须通过服务器进行代理。我在 React 中使用 react-google-login 包。它发送用户登录,并返回一个用户对象(包括令牌)。我正在将该令牌发送到我的服务器,以使用 Passport 对照我的客户端密码验证它。
  • @sideshowbarker 如果我从保护 /oauth/google 路由中删除 Passport,则该路由会收到一个请求,该请求的正文包含用户的访问令牌且没有错误。所以我不得不假设 Passport 正在向https://accounts.google.com/o/oauth2/v2/auth 发出请求,这导致了错误。对于我的生活,我找不到这种流程的直接工作示例。这让我感到困惑,因为 React、Node、Express、Passport 似乎都是行业标准。

标签: node.js express oauth-2.0 cors passport.js


【解决方案1】:

CORS 与向 google 提出请求无关,因为当您在 console.developers.google.com 中注册您的应用时,它已由 google 处理。 p>

CRA 开发者服务器express api 服务器 之间存在问题。您正在从 localhost:3000localhost:5000 发出请求。要解决此问题,请使用代理。

在客户端目录中:

npm i http-proxy-middleware --save

client/src 中创建 setupProxy.js 文件。无需在任何地方导入。 create-react-app 会寻找这个目录

将您的代理添加到此文件:

module.exports = function(app) {
    app.use(proxy("/auth/google", { target: "http://localhost:5000" }));
    app.use(proxy("/api/**", { target: "http://localhost:5000" }));
};

我们说的是做一个代理,如果有人试图访问我们的反应服务器上的路由 /api 或 /auth/google,自动将请求转发到 localhost:5000

这里是更多详细信息的链接:

https://create-react-app.dev/docs/proxying-api-requests-in-development/

默认情况下,password.js 不允许代理请求。

passport.use('googleVerification', new GoogleStrategy({
    clientID: process.env.OAUTH_CLIENT_ID,
    clientSecret: process.env.OAUTH_SECRET,
    callbackURL: 'http://localhost:5000/api/oauth/google/return',
    proxy:true
}

这里很重要的一点是,您应该了解为什么要使用代理。据我从您的代码中了解,从浏览器中,您提出了 express 请求,express 将使用 password.js 处理身份验证。在password.js运行完所有的认证步骤后,它会创建一个cookie,把id塞进去,交给express,express会发送到浏览器。这是您的应用结构:

 BROWSER ==> EXPRESS ==> GOOGLE-SERVER

浏览器会自动将 cookie 附加到发出 cookie 的服务器的每个请求中。所以浏览器知道哪个cookie属于哪个服务器,所以当他们向那个服务器发出新请求时,它们会附加它。但是在您的应用程序结构中,浏览器不与 GOOGLE-SERVER 对话。如果你没有使用代理,你会通过 express 从 GOOGLE-SERVER 获取 cookie,但由于你没有向 GOOGLE-SERVER 发出请求,cookie 不会被使用,它不会被自动附加。这就是使用 cookie 的重点,浏览器会自动附加 cookie。通过设置代理,现在浏览器不知道 GOOGLE-SERVER。据它所知,它正在向快递服务器发出请求。所以每次浏览器请求使用相同的端口表达时,它都会附加cookie。我希望这部分很清楚。

现在 react 只与 express-server 通信。

  BROWSER ==> EXPRESS

由于 react 和 exress 不在同一个端口上,你会得到 cors 错误。

有两种解决方案。 1 是使用cors 包。

它的设置非常简单

var express = require('express')
var cors = require('cors')
var app = express()
 
app.use(cors()) // use this before route handlers

第二种解决方案是在路由处理程序之前手动设置中间件

app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader(
    "Access-Control-Allow-Methods",
    "OPTIONS, GET, POST, PUT, PATCH, DELETE"
  );
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  next(); // dont forget this
});

【讨论】:

  • 如果我的后端应用程序完全独立于我的前端应用程序运行怎么办?假设我的前端在 Firebase 上运行,而我的后端在 Heroku 上,代理还能工作吗?提前致谢!
  • @Yilmaz 我试过使用这个解决方案但没有成功。我在这里(stackoverflow.com/questions/65033944/…)上发表了一篇关于它的帖子,但是代理似乎并没有起到反应的作用
  • @asus 我详细更新了这个问题。我希望这能解决问题
  • 对于您的第二个解决方案,它会去哪里?在快速应用程序的主入口点还是在控制器中?
  • @asus 在主应用程序中。第二种解决方案是常规中间件
【解决方案2】:

你不能对 /oauth/google 路由进行 axios 调用!
这是我的解决方案...代码略有不同,但您会明白这个概念。

// step 1:
// onClick handler function of the button should use window.open instead 
// of axios or 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 端点可用于收集用户信息以更新反应状态
步骤 4 调用端点,获取数据并更新用户状态。

【讨论】:

    猜你喜欢
    • 2021-08-27
    • 2019-03-14
    • 2014-06-07
    • 2016-03-27
    • 2019-09-15
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    • 1970-01-01
    相关资源
    最近更新 更多