使用 Express 和 MongoDB - 如何注销用户?

Posted

技术标签:

【中文标题】使用 Express 和 MongoDB - 如何注销用户?【英文标题】:Using Express and MongoDB - How do I log out a User? 【发布时间】:2020-09-27 21:46:33 【问题描述】:

我正在关注Authentication in NodeJS With Express and Mongo - CodeLab #1的教程

我让一切都能完美运行,但本教程没有说明如何注销用户。

据我所知,会话保存在 Mongoose Atlas 上,这是我正在使用的数据库。当我使用 Postman 登录用户时,我会得到一个令牌。但我不确定如何配置 /logout 路由。

这是我的代码:

//routes/user.js
const express = require("express");
const  check, validationResult  = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const router = express.Router();
const auth = require("../middleware/auth");

const User = require("../models/User");

/**
 * @method - POST
 * @param - /signup
 * @description - User SignUp
 */

//Signup
router.post(
  "/signup",
  [
    check("username", "Please Enter a Valid Username")
      .not()
      .isEmpty(),
    check("email", "Please enter a valid email").isEmail(),
    check("password", "Please enter a valid password").isLength(
      min: 6
    )
  ],
  async (req, res) => 
    const errors = validationResult(req);
    if (!errors.isEmpty()) 
      return res.status(400).json(
        errors: errors.array()
      );
    

    const 
      username,
      email,
      password
     = req.body;
    try 
      let user = await User.findOne(
        email
      );
      if (user) 
        return res.status(400).json(
          msg: "User Already Exists"
        );
      

      user = new User(
        username,
        email,
        password
      );

      const salt = await bcrypt.genSalt(10);
      user.password = await bcrypt.hash(password, salt);

      await user.save();

      const payload = 
        user: 
          id: user.id
        
      ;

      jwt.sign(
        payload,
        "randomString", 
        expiresIn: 10000
      ,
        (err, token) => 
          if (err) throw err;
          res.status(200).json(
            token
          );
        
      );
     catch (err) 
      console.log(err.message);
      res.status(500).send("Error in Saving");
    
  
);

// Login
router.post(
  "/login",
  [
    check("email", "Please enter a valid email").isEmail(),
    check("password", "Please enter a valid password").isLength(
      min: 6
    )
  ],
  async (req, res) => 
    const errors = validationResult(req);

    if (!errors.isEmpty()) 
      return res.status(400).json(
        errors: errors.array()
      );
    

    const  email, password  = req.body;
    try 
      let user = await User.findOne(
        email
      );
      if (!user)
        return res.status(400).json(
          message: "User Not Exist"
        );

      const isMatch = await bcrypt.compare(password, user.password);
      if (!isMatch)
        return res.status(400).json(
          message: "Incorrect Password !"
        );

      const payload = 
        user: 
          id: user.id
        
      ;

      jwt.sign(
        payload,
        "randomString",
        
          expiresIn: 3600
        ,
        (err, token) => 
          if (err) throw err;
          res.status(200).json(
            token
          );
        
      );
     catch (e) 
      console.error(e);
      res.status(500).json(
        message: "Server Error"
      );
    
  
);

// router.route("/logout").get(function (req, res, next) 
//   if (expire(req.headers)) 
//     delete req.user;
//     return res.status(200).json(
//       "message": "User has been successfully logged out"
//     );
//    else 
//     return next(new UnauthorizedAccessError("401"));
//   
// );

router.get("/me", auth, async (req, res) => 
  try 
    // request.user is getting fetched from Middleware after token authentication
    const user = await User.findById(req.user.id);
    res.json(user);
   catch (e) 
    res.send( message: "Error in Fetching user" );
  


);

router.get('/logout', isAuthenticated, function (req, res) 
  console.log('User Id', req.user._id);
  User.findByIdAndRemove(req.user._id, function (err) 
    if (err) res.send(err);
    res.json( message: 'User Deleted!' );
  )
);


module.exports = router;

function isAuthenticated(req, res, next) 
  console.log("req: " + JSON.stringify(req.headers.authorization));
  // if (!(req.headers && req.headers.authorization)) 
  //   return res.status(400).send( message: 'You did not provide a JSON web token in the authorization header' );
  //
;
///middleware/auth.js
const jwt = require("jsonwebtoken");

module.exports = function (req, res, next) 
  const token = req.header("token");
  if (!token) return res.status(401).json( message: "Auth Error" );

  try 
    const decoded = jwt.verify(token, "randomString");
    req.user = decoded.user;
    next();
   catch (e) 
    console.error(e);
    res.status(500).send( message: "Invalid Token" );
  
;
///models/User.js
const mongoose = require("mongoose");

const UserSchema = mongoose.Schema(
  username: 
    type: String,
    required: true
  ,
  email: 
    type: String,
    required: true
  ,
  password: 
    type: String,
    required: true
  ,
  createdAt: 
    type: Date,
    default: Date.now()
  
);

// export model user with UserSchema
module.exports = mongoose.model("user", UserSchema);

所以我的问题是,如何实现 /logout 路由,以便如果用户单击注销按钮并调用该路由,他们的令牌会被销毁。我只问后端部分。我可以使用 axios 处理。

谢谢。

【问题讨论】:

【参考方案1】:

当您使用 JWT 时,后端将始终检查 2 件事 1.适当的令牌 2.如果那个特定的时间结束了(你应该处理这个)

对于第二点,如果用户时间比从前端开始,如果您已将令牌存储在本地存储中,您可以删除令牌。

对于用户点击注销时的注销,只需从本地存储中删除 jwt 并重定向到登录或其他页面

【讨论】:

我没有将令牌存储在任何地方。也许这就是问题所在。我假设令牌是由 Mongoose 从 /login 路由中存储的。现在我更仔细地查看了该代码,看起来令牌刚刚生成,但我仍然需要存储它。我有这个权利吗? /me 路由从哪里得到它的信息? /me 路由根据令牌返回登录用户。 @David.Warwick 您可以将令牌保存在 req.header.authorization 中,并通过解密用户令牌来获取用户信息(您可以将用户信息保存在令牌中)【参考方案2】:

据我所知,您没有在任何地方保存任何会话数据或存储令牌 - 这很好。您只是将令牌附加到 API 请求的标头中。

所以你唯一能做的就是让/logout route 中的令牌过期 然后确保您删除客户端上的令牌 - 可能是 localStorage、sessionStorage 等 - 您的客户端代码需要终止令牌,因此不能再次包含它。

旁注:

    您不会在任何地方延长令牌的生命周期,因此即使用户继续在网站上进行交互,令牌过期时间也不会更新。您将需要手动刷新令牌/生成新令牌以实现滑动到期。

    我建议您将令牌保存在 cookie 中。将 cookie 设置为 HttpOnly、Secure,并指定域。这更加安全,并且还允许您使 API 中的 cookie 过期。如果您包含的任何脚本遭到入侵,他们可以轻松访问您所有用户的令牌。

例子:

import serialize from 'cookie';
import jsend from 'jsend';

...
const token = jwt.sign(
    
        id: validationResult.value.id // whatever you want to add to the token, here it is the id of a user
    ,
    privateKeyBuffer,
    
        expiresIn: process.env.token_ttl,
        algorithm: 'RS256'
    );

const cookieOptions = 
    httpOnly: true,
    path: '/',
    maxAge: process.env.token_ttl,
    expires: new Date(Date.now() + process.env.token_ttl),
    sameSite: process.env.cookie_samesite, // strict
    domain: process.env.cookie_domain, // your domain
    secure: process.env.cookie_secure // true
;

const tokenCookie = await serialize('token', token, cookieOptions);

res.setHeader('Set-Cookie', [tokenCookie]);

res.setHeader('Content-Type', 'application/json');
res.status(200).json(jsend.success(true));

然后退出:

    // grab from req.cookies.token and validate
    const token = await extractToken(req);

    // you can take action if it's invalid, but not really important
    if(!token) 
       ...
    

    // this is how we expire it - the options here must match the options you created with!
    const cookieOptions = 
        httpOnly: true,
        path: '/',
        maxAge: 0,
        expires: 0,
        sameSite: process.env.cookie_samesite, // strict
        domain: process.env.cookie_domain, // your domain
        secure: process.env.cookie_secure // true
    ;

    // set to empty 
    const tokenCookie = await serialize('token', '', cookieOptions);

    res.setHeader('Set-Cookie', [tokenCookie]);
    res.setHeader('Content-Type', 'application/json');
    res.status(200).json(jsend.success(true));

【讨论】:

好的。看来我还有一些东西要在这里学习。所以我知道如何使用本地存储。我仍然需要弄清楚如何使令牌过期,这是我似乎无法弄清楚的。我想从我的 React 前端,我会从响应中获取令牌,然后将其保存到本地存储中。然后我必须弄清楚如何更新本地存储中的到期日期。你能告诉我如何在注销路由中使令牌过期吗? 如果它解决了您的问题并回答了您的问题,请记得accept the answer。 谢谢塞缪尔。我会试一试。感谢您的耐心等待。

以上是关于使用 Express 和 MongoDB - 如何注销用户?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 nodejs / express 将数据存储在 mongodb 中?

如何填充递归模式引用 Mongodb、Mongoose、Express?

如何使用 mongodb、node.js、express 和 EJS 创建动态站点地图?

如何使用特定集合 Mongoose-Express-Mongodb (MEAN STACK)

如何使用 MongoDB + NodeJS Express 向 ReactJS React Router 和 Redux 添加登录身份验证和会话?

Node.JS、Express 和 MongoDB :: 多个集合