首页 > 解决方案 > React 身份验证会话管理

问题描述

我正在开发这个由两部分组成的应用程序(React + Express... + Apollo(用于 GraphQL))。为了管理身份验证,我一直在阅读指南和观看视频,并且使用JWT 令牌上下文 API取得了一些进展,例如:

  1. 每当用户登录时,React 都会使用gql(由apollo-boost提供支持)向 Express 发出请求。
  2. Express 服务器(后端)向其发送响应,并使用解析器处理其数据:
require('dotenv').config();
import { User } from '../models/User';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';

export default {
  Query: {
    login: async (_, { email, password }, {req}) => {
      try {
        const user = await User.findOne({ email });
        if (!user) {
          throw new Error('User don\'t exist');
        }

        const isEqual = await bcrypt.compare(password, user.password);
        if (!isEqual) {
          throw new Error('Wrong credentials');
        }

        const {
          JWTSECRET = 'secret'
        } = process.env;

        const token = jwt.sign({ userId: user.id, email: user.email }, JWTSECRET, {
          expiresIn: '1h'
        });

        return {
          userId: user.id,
          token,
          tokenExpiration: 3600000 // Date.now()+3600000 doesn't work cause Int is 32-bit signed
        }

      } catch (e) {
        throw new Error(e.message);
      }
    }
  }
};

const hashPassword = async password => {
  const saltRounds = 10;

  const hashedPassword = await new Promise((resolve, reject) => {
    try {
      bcrypt.hash(password, saltRounds, (err, hash) => {
        if (err) reject(err);
        resolve(hash);
      });
    } catch (e) {
      throw new Error(e.message);
    }
  });

  return hashedPassword;
};
  1. React 获取响应并将其附加到上下文中,我猜它还会分配一些localStorage东西来持久化会话,例如:
import React from 'react';
import AuthContext from './auth-context';
import ApolloClient, { gql } from 'apollo-boost';

function getClient() {
    return new ApolloClient({ uri: 'http://localhost:4000/ws', credentials: 'same-origin' });
}

const AuthState = props => {
    const defaultValue = {
        userId: null,
        userFirstName: null,
        userLastName: null,
        userEmail: null,
        token: null,
        tokenExpiration: null,
        createdOn: null,
        errors: [],
        login,
        get,
    };

    return (
        <AuthContext.Provider value={defaultValue}>
            {props.children}
        </AuthContext.Provider>
    );

    /**
     * Login
     * @param {string} email 
     * @param {string} password 
     */
    async function login(email, password) {
        const client = getClient();
        const CHECK_CREDENTIALS = gql`
            query {
                login(email: "${email}", password: "${password}") {
                    userId
                    token
                    tokenExpiration
                }
            }
        `;
        const { data, loading, errors } = await client.query({
            query: CHECK_CREDENTIALS,
            errorPolicy: 'all'
        });

        if (loading) {
            defaultValue.message = "Validating credentials";
        }

        if (errors) {
            defaultValue.errors = errors.map(error => <p>{error.message}</p>);
        }

        if (data && data.login) {
            defaultValue.userId = data.login.userId;
            defaultValue.token = data.login.token;
            defaultValue.tokenExpiration = data.login.tokenExpiration;
            // Persist session ?
            localStorage.setItem('user-token', data.login.token);
            localStorage.setItem('user-token-expiration', data.login.tokenExpiration);
        }
    }

    async function get(userId) {
        const client = getClient();
        const GET_USER = gql`
            query {
                user(_id: "${userId}") {
                    firstName
                    lastName
                    email
                }
            }
        `;

        const { data, loading, errors } = await client.query({
            query: GET_USER,
            errorPolicy: 'all'
        });

        if (loading) {
            defaultValue.message = "Getting user";
        }

        if (errors) {
            defaultValue.errors = errors.map(error => <p>{error.message}</p>);
        }

        if (data && data.user) {
            defaultValue.userFirstName = data.user.firstName;
            defaultValue.userLastName = data.user.lastName;
            defaultValue.userEmail = data.user.email;
        }
    }
}

export default AuthState;
  1. 在每个需要身份验证的组件中,我都会检查context.tokencontext.tokenExpiration重定向到登录或让它们通过。

我一直想知道如果我使用开发者控制台手动设置一些user-tokenuser-token-expiration?我错过了什么?

任何意见表示赞赏。

标签: javascriptnode.jsreactjsexpressjwt

解决方案


好吧...我一直在研究代码,并验证了login解析器使用秘密签名来验证令牌。

login: async (_, { email, password }, {req}) => {
  try {
    const user = await User.findOne({ email });
    if (!user) {
      throw new Error('wrong credentials');
    }

    const isEqual = await bcrypt.compare(password, user.password);
    if (!isEqual) {
      throw new Error('wrong credentials');
    }

    const {
      JWTSECRET = 'secret'
    } = process.env;

    const token = jwt.sign({ userId: user.id, email: user.email }, JWTSECRET, {
      expiresIn: '1h'
    }); 

    return {
      userId: user.id,
      token,
      tokenExpiration: Date.now() + 3600000
    }

  } catch (e) {
    throw new Error(e.message);
  }
}

为了看到实际效果,在生成令牌后转到JWT.IO 官方页面并将其粘贴到那里。然后在验证签名字段中插入“秘密”或任何内容,它会将Invalid signature更改为Verified


推荐阅读