Heroku 谷歌 OAuth 错误

3
我遇到了一个非常令人沮丧的错误。在本地开发环境中,当我使用passport身份验证进行谷歌OAuth请求时,它能够完美工作。但是将其推送到Heroku上时,返回状态码200而不是在开发环境中得到的状态码302,这会让我重定向到谷歌OAuth登录页面。屏幕只是显示空白,没有任何错误信息。我尝试故意使用错误的客户端ID来发起请求,但它甚至都没有注册这个请求。 登录 在Heroku上,这个链接会带我进入一个空白页面,而且根本没有注册任何请求。 请帮忙解决! 服务器端护照:
 // .use is generic register
passport.use(
  new GoogleStrategy(
    {
      clientID: keys.googleClientID,
      clientSecret: keys.googleClientSecret,
      // need url for where user should go on callback after they grant permission to our application on google auth page
      callbackURL: "/auth/google/callback",
      // have to authorize this callback url in the google oauth console.developors screen because of security reasons
      proxy: true // trust the proxy our request runs through so heroku callbacks to the correct url
    },
    async (accessToken, refreshToken, profile, done) => {
      // after authenticated on the next get request to google it will call this with the accessToken, aka callback function
      // console.log("access token", accessToken);
      // console.log("refresh token", refreshToken);
      // console.log("profile", profile);

      // check to see if user id already exists before saving it to DB so it does not overlap...mongoose query...asynchronous operation
      // using async await
      const existingUser = await User.findOne({
        googleId: profile.id
      });
      // get promise response
      if (existingUser) {
        // already have record
        // finish passport auth function
        return done(null, existingUser); // passes to serialize user so serialize can pull that user id
      }
      // we don't have a new record so make one
      const user = await new User({
        // creates new model instance of user
        googleId: profile.id
      }).save(); // have to save it to DB
      // get promise from save since asynchronize, then finish with response
      done(null, user); // passes to serialize user so serialize can get that id
    }
  )
); // create new instance of GoogleStrategy

服务器端API:

    app.get(
    "/auth/google", // passport, attempt to authenticate the user coming in on this route
    passport.authenticate("google", {
      // google strategy has internal code, that is 'google', so passport will know to find the google passport authenticator
      scope: ["profile", "email"] // options object
      // specifies to google we want access to this users profile and email information from their account, these are premade strings in the google oauth process not made up
    })
  );

  // in this callback route they are going to have the code, and google will see that and it will handle it differnetly by exchanging the code for an actual profile, it will call the next part of the GoogleStrategy, aka the accessToken to be saved to Database

  // @route GET auth/google/callback
  // @desc  Get callback data from google to redirect user if signed in
  // @access Private can only access this after signed in

  app.get(
    "/auth/google/callback",
    passport.authenticate("google"),
    // after authenticate process is done, send user to correct route
    (req, res) => {
      // redirect to dashboard route after sign-in
      res.redirect("/surveys");
      // full HTTP requrest, so it reloads versus AJAX request which uses react and redux and is much faster
    }
  );

客户端(Client) - 端
<div
            className="collapse navbar-collapse nav-positioning"
            id="navbarNav"
          >
            <ul className="navbar-nav">
              <li className="nav-item google-link">
                <a className="nav-link" href="/auth/google">
                  Google Login
                </a>
              </li>
            </ul>
          </div>

Index.js

// Route file, or starter file
const express = require("express");
// node.js does not have support from E6,
// so we use common js modules
// import vs require :
// common vs ES6

// bring in mongoose
const mongoose = require("mongoose");

// tell express it must make use of cookies when using passport
const cookieSession = require("cookie-session");
const passport = require("passport");

// pull in body-parser middleware to get req.body
const bodyParser = require("body-parser");

// connect it to DB in keys so it is not posted to github
const keys = require("./config/keys");

//connect mongoose
mongoose.connect(keys.mongoURI);

// ########## MODELS ################
// THIS MUST BE ABOVE WHERE YOU USE IT, SO ABOVE PASSPORT
require("./models/User");
require("./models/Survey");
// don't have to require recipient because its included inside Survey

// pull in passport service, we are not returning anything in passport, so we do not need const passport because nothing to assign
require("./services/passport");

// Generate a new application that represents a running express app
const app = express(); // vast majority use single app
// this will listen for incoming requests, and route them on to different route handlers

// parser so every time a req has a req.body comes in then it will be assigned to the req.body property
app.use(bodyParser.json());

app.use(
  cookieSession({
    // age for auth cookies to last... 30 days
    maxAge: 30 * 24 * 60 * 60 * 1000,
    // give cookie a key
    keys: [keys.cookieKey]
  })
);

// tell passport to use cookies
app.use(passport.initialize());
app.use(passport.session());
// done with authentication flow

//require that file returns a function, which is then immediately called with the app object
require("./routes/authRoutes")(app);
require("./routes/billingRoutes")(app);
require("./routes/surveyRoutes")(app);

if (process.env.NODE_ENV === "production") {
  // if in production make sure express will serve up production assets
  // like main.js
  app.use(express.static("client/build"));

  // Express will serve up index.html file if it doesn't recognize the routes
  const path = require("path");

  app.get("*", (req, res) => {
    res.sendFile(path.resolve(__dirname, "client", "build", "index.html"));
  });
}

// dynamically figure out what port to listen to... Heroku, heroku will inject env variables in moment of deploy, but only works in production not development environment
const PORT = process.env.PORT || 5000; // if heroku port exists assign it that, else, assign it 5000

app.listen(PORT); // listen for requests and route them to the correct handler on port 5000

/* ###### HEROKU PREDEPLOY ##### */
// specifiy node version and start script for heroku in package.json
// make .gitignore for dependencies which should not be committed on deploy, heroku will install them itself

// app.use wires up middleware for our application

// ############### TIPS
/*
 Google first, because its been asked before...
 Run in module
 */

你能否请发一下 /auth/google 的客户端代码以及服务器端API呢? - yaswanth
抱歉耽搁了,已发布。 - Noah Kreiger
你的 Google 客户端密钥已配置为与 Heroku 域名一起使用了吗? - yaswanth
是的,它们是...即使它们不是,我也会收到授权错误吗?它只是把我带到一个空白屏幕。再次说明,在本地工作正常,但在Heroku上不行。我可能缺少生产设置吗? - Noah Kreiger
你需要进入浏览器开发工具并查看网络选项卡,以了解发生了什么。 - pinoyyid
显示剩余2条评论
3个回答

2
我也遇到了同样的问题。我通过在配置键上定义 absoluteURI 来解决它,因为谷歌查看 https:// 的 url 回调而 heroku 的路径是 http:// 的,您可以通过添加 proxy: true 来修复这个问题,但实际上并没有起作用。
在配置键中添加:
开发环境:absoluteURI: localhost:5000
生产环境:absoluteURI: http://herokupath
// .use is generic register
passport.use(
  new GoogleStrategy(
    {
      clientID: keys.googleClientID,
      clientSecret: keys.googleClientSecret,
      callbackURL: absoluteURI + "/auth/google/callback",
      proxy: true 
    },

1
我认为问题在于你在Heroku上的应用只监听http请求。如果你到OAuth页面的链接形式为"https://your-domain.com/auth/google",那么你的应用程序路由将无法匹配该路由(因为https),因此你的应用程序将显示一个空白页面,就像它对任何未监听的路由一样。
解决这个问题的一种方法是仍然使用https(因此仍然在url旁显示安全标志),除了这个OAuth链接以外,所有链接都使用https。你在应用程序中的get和post请求将使用http,但是在url上可见的任何链接都将使用https。类似这样的东西可以工作:
app.use(function(req, res, next) {
        if (process.env.NODE_ENV === "production") {
            const reqType = req.headers["x-forwarded-proto"];
            // if not https redirect to https unless logging in using OAuth
            if (reqType !== "https") {
                req.url.indexOf("auth/google") !== -1
                  ? next()
                  : res.redirect("https://" + req.headers.host + req.url);
            } 
        } else {
            next();
        }
    });  

任何指向OAuth登录页面的前端链接都应该是一个HTTP链接。

0
请看一下这个SO答案。看起来你的范围参数需要修改才能使Google认证正常工作。

请看下面的注释,这是另一种设置作用域的方法。我会测试这种方法,也许在开发中可以工作,但在Heroku上不行。 - Noah Kreiger
还是不行,我不确定出了什么问题,因为没有显示错误信息。 - Noah Kreiger
它似乎几乎没有触发我的API请求。 - Noah Kreiger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接