【问题标题】:Using Express and MongoDB - How do I log out a User?使用 Express 和 MongoDB - 如何注销用户?
【发布时间】: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 处理。

谢谢。

【问题讨论】:

    标签: javascript node.js mongodb express jwt


    【解决方案1】:

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

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

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

    【讨论】:

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

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

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

    旁注:

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

    2. 我建议您将令牌保存在 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
    • 谢谢塞缪尔。我会试一试。感谢您的耐心等待。
    猜你喜欢
    • 2023-03-31
    • 2023-04-10
    • 2014-07-17
    • 2012-06-13
    • 1970-01-01
    • 2020-11-12
    • 1970-01-01
    • 1970-01-01
    • 2010-11-26
    相关资源
    最近更新 更多