首页 > 解决方案 > http-only cookie + token:双重工作?

问题描述

亲爱的,

我试图找到如何使用服务器发送的仅 hhtp cookie 在客户端管理身份验证。

我不明白的是,由于前端无法访问仅 HTTP 的 cookie,前端如何知道用户(仍然)经过身份验证?

到目前为止,唯一的解决方案是在身份验证成功时向客户端发送令牌。并将此令牌保存在客户端创建的第二个 cookie 中。

但在我看来,我做了两次同样的工作。

1-在服务器端管理仅 HTTP cookie,尤其是到期日期 2-也在客户端管理第二个 cookie 的到期日期。

怎样才能避免这种情况?我想基于 HTTP only server cookie 管理客户端的身份验证。如果有服务器 cookie,则继续,否则重定向到登录页面。

我在服务器端使用 node/express 并对客户端做出反应。会话存储在redis中,双方都是HTTPS使用证书。

谢谢

标签: node.jsreactjsexpresscookieshttps

解决方案


您不需要存储另一个 cookie。我想您在端点上使用基于令牌的身份验证,例如。智威汤逊。然后你想想这个场景:

  1. 用户将用户名/密码发送到服务器。
  2. 检查用户凭据,如果有效,则使用令牌创建仅限 http 的 cookie
    const user = await getUser({ where: { email } });

    const valid = await bcrypt.compare(password, user.password);
    if (!valid) {
      throw new UserInputError('Form Arguments invalid', {
        invalidArgs: {
          'password': 'Invalid password!',
        },
      });
    }

    const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
    /
    res.cookie('token', token, {
      httpOnly: true,
      maxAge: 1000 * 60 * 60 * 24 * 365,
    });

  1. 编写 auth 中间件,将 userId 放到 req 上,以便将来请求访问
const jwt = require('jsonwebtoken');
const { AuthenticationError } = require('apollo-server');

module.exports = async function(req, res, next) {
  const { token } = req.cookies;

  if (token) {
    try {
      const { userId } = jwt.verify(token, process.env.APP_SECRET);

      if (!userId) return next();
      req.userId = userId;

    } catch (e) {
      console.log(e);
    }
  }

  next();
};
  1. 检查每个请求的用户 ID。如果没有userId,用户没有登录
  if (!req.userId) {
     throw new AuthenticationError('Log in!');
   }
  1. 如果用户的令牌无效/过期,您将收到 AuthenticationError。抓住它并重定向到登录页面。
  2. 如果您的 UI 取决于用户状态,您可以创建易于使用的组件(我正在使用 React)来检查它。

用户组件:

import { Query } from 'react-apollo';
import gql from 'graphql-tag';
import PropTypes from 'prop-types';

const CURRENT_USER_QUERY = gql`
  query CURRENT_USER_QUERY {
    me {
      userId
      firstName
      lastName
      profilePictureUrl
    }
  }
`;

const User = props => (
  <Query {...props} query={CURRENT_USER_QUERY} fetchPolicy={'cache-first'}>
    {payload => props.children(payload)}
  </Query>
);

User.propTypes = {
  children: PropTypes.func.isRequired,
};

export default User;

如果我们me从服务器获取对象,你知道,有一个登录的用户,所以你可以渲染取决于用户的状态:

import { Link } from 'react-router-dom';
import React from 'react';

<User>
  {({ loading, error, data: { me } }) => {
    if (loading || error || !me) return (
      <Button component={Link} to={'/login'}>Login</Button>
    );
    if(me) return (
      <Button component={Link} to={'/dashboard'}>Go to dashboard</Button>
    )
  }}
</User>

推荐阅读