Passport.js、会话和反应 |从 /login 路由以外的任何路由访问 req.user 是未定义的

Posted

技术标签:

【中文标题】Passport.js、会话和反应 |从 /login 路由以外的任何路由访问 req.user 是未定义的【英文标题】:Passport.js, Sessions, & React | Accessing req.user from any route other than the /login route is undefined 【发布时间】:2020-11-18 23:41:12 【问题描述】:

我在 localhost:3000 有一个 React 客户端设置,在 localhost:5000 有一个 node.js 服务器

我正在尝试一个简单的身份验证流程,其中客户端尝试使用 Passport.js Google OAuth2.0 对服务器进行身份验证,并使用与 MongoDB 存储的快速会话保持身份验证。

我认为我发现 req.user 未定义的原因是因为我对授权流程应该如何工作以及实际代码的任何问题缺乏了解。

我正在通过 react 客户端中的以下代码启动身份验证流程:

<Button href="http://localhost:5000/auth/google">
        Login using Google
</Button>

以下是我的 auth.js 文件:

const express = require("express");
const passport = require("passport");
const router = express.Router();

// @desc    Auth with Google
// @route   GET /auth/google
router.get("/google", passport.authenticate("google",  scope: ["profile"] ));

// @desc    Google auth callback
// @route   GET /auth/google/callback
router.get(
    "/google/callback",
    passport.authenticate("google", 
        failureRedirect: "/",
        successRedirect: "http://localhost:3000/dashboard",
    )
);

// @desc    Logout user
// @route   /auth/logout
router.get("/logout", (req, res) => 
    req.logout();
    res.redirect("/");
);

module.exports = router;

以下是我的谷歌策略配置:

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

module.exports = function (passport) 
    passport.use(
        new GoogleStrategy(
            
                clientID: process.env.GOOGLE_CLIENT_ID,
                clientSecret: process.env.GOOGLE_CLIENT_SECRET,
                callbackURL: "http://localhost:5000/auth/google/callback",
            ,
            async (accessToken, refreshToken, profile, done) => 
                const newUser = 
                    googleId: profile.id,
                    displayName: profile.displayName,
                    firstName: profile.name.givenName,
                    lastName: profile.name.familyName,
                    image: profile.photos[0].value,
                ;

                try 
                    let user = await User.findOne( googleId: profile.id );

                    if (user) 
                        done(null, user);
                     else 
                        user = await User.create(newUser);
                        done(null, user);
                    
                 catch (err) 
                    console.error(err);
                
            
        )
    );

    passport.serializeUser((user, done) => 
        done(null, user.id);
    );

    passport.deserializeUser((id, done) => 
        User.findById(id, (err, user) => done(err, user));
    );
;

以下代码是我的 index.js,它将所有内容组合在一起:

const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const connectDB = require("./config/db");
const morgan = require("morgan");
const passport = require("passport");
const session = require("express-session");
const MongoStore = require("connect-mongo")(session);

// Dotenv config
const dotenv = require("dotenv").config(
    path: "./config/config.env",
);

// Passport config
require("./config/passport")(passport);

// MongoDB config
connectDB();

const app = express();
const PORT = process.env.PORT;

// Middleware

app.use(cors());
app.use(bodyParser.json());
app.use(morgan("dev"));

// Sessions
app.use(
    session(
        secret: "***",
        resave: false,
        saveUninitialized: false,
        store: new MongoStore( mongooseConnection: mongoose.connection ),
    )
);

// Passport middleware
app.use(passport.initialize());
app.use(passport.session());

app.use("/posts", require("./routes/posts"));
app.use("/auth", require("./routes/auth"));

app.listen(PORT, () => console.log(`Server listening @ port $PORT`));

我在用户登录后通过其中一条路由从数据库中获取帖子:

...
// @desc Get all posts
// @route GET /posts

router.get("/", (req, res) => 
    const posts = Post.find(function (error, posts) 
        if (error) return console.error(error);
        console.log(req.user) // <------ is undefined
        res.json(posts);
    );
);

我假设在用户被重定向到仪表板然后向路由发送请求以获取所有帖子后,用户未通过身份验证?虽然我在验证用户身份后将用户重定向到这条路线?

目标绝对不是在 /posts 路由获取用户,而是创建一个单独的 /user 路由将用户返回到客户端,但这也会导致 req.user 未定义。

【问题讨论】:

我认为您只是缺少 / GET 路由的护照身份验证中间件。如果你想默认应用一个中间件到所有路由,你可以使用app.use( ...) @PascalLamers 我不确定那是哪个中间件?如果值得一提的是,直接通过浏览器访问/ GET 路由(通过转到 localhost:5000/posts),它会成功记录用户,但如果通过客户端访问则不会。我敢打赌,客户端没有经过身份验证,但我不知道该怎么办? 可能是这样,所以身份验证通过 :5000 进行。这是 Facebook 身份验证流程的示例(类似)。 github.com/passport/express-4.x-facebook-example 。他们没有使用完整的 URL 进行回调。你可以试试或者在回调中使用 :3000 ? 我确实尝试在回调中只使用路由而不是完整的 URL。不幸的是,这没有用。我不确定回调中是否有:3000,因为回调必须通过服务器@端口 5000。谢谢你的例子。唯一的区别是他们通过服务器呈现页面,而不是按照我的方式设置客户端-服务器。 啊,我想我记得我在客户端呈现的站点中使用护照时遇到了类似的问题。一个好主意是使用端口的环境变量(开发与生产),这样您就不需要一直更改它。我做的另一件事是在回调控制器中,使用 accessToken 作为查询参数重定向到前端,这样我就可以访问它和客户端应用程序。但是我在做事时使用了一种不基于 cookie 的方法。您是仅使用 Google 对用户进行身份验证,还是打算同时代表用户使用 Google API? 【参考方案1】:

起初haidousm的回答对我不起作用,我所做的是将 withCredentials: true 添加到我的登录请求中。

【讨论】:

【参考方案2】:

终于搞定了。

我必须编辑两件事,

首先(服务器端):

我必须设置 CORS 以允许 cookie 这样通过:

app.use(
    cors(
         origin: "http://localhost:3000", // allow to server to accept request from different origin
         methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
         credentials: true, // allow session cookie from browser to pass through
   )
);

第二(客户端):

我必须让 Axios 知道它必须将 cookie 与请求一起发送:

axios
     .get("http://localhost:5000/auth/user",  withCredentials: true )
     .then(console.log)
     .catch(console.error);

/auth/user 路由的定义如下:

router.get("/user", (req, res) => 
    if (req.user) 
        res.json(req.user);
    
);

如果我对整个身份验证过程有更好的了解,这些错误是可以避免的,现在我知道了。

我们生活,我们学习。

【讨论】:

感谢您发布答案,您为我节省了大量时间。

以上是关于Passport.js、会话和反应 |从 /login 路由以外的任何路由访问 req.user 是未定义的的主要内容,如果未能解决你的问题,请参考以下文章

Passport.js 策略在不使用会话时失败

如何使用 cookie-session 和 passport.js 在注册时启动会话?

快速会话与passport.js?

Passport js 无法跨域维护会话

没有会话的 Passport js 身份验证

Nodejs + Passport.js + Redis:如何在 Redis 中存储会话