首页 > 解决方案 > 用 ApolloClient 正确实现 refreshtoken

问题描述

我试图在接近到期时间时刷新身份验证令牌,但是我最终陷入了无限循环。期望在App.js中检查过期时间的代码应该可以工作并重置身份验证令牌和刷新令牌,但是会开始无休止地循环。

我有以下Auth 辅助函数

const jwt = require("jsonwebtoken");
const User = require("../models/User");
const { AuthenticationError } = require("apollo-server-express");
const config = require("config");
const jwtSecret = config.get("jwt_secret");
const jwtRefreshTokenSecret = config.get("jwt_refresh_token");

const authFunctions = {
  checkSignedIn: async (req, requireAuth = true) => {
    const header = req.headers.authorization;
    if (header) {
      const token = header.replace("Bearer ", "");
      const decoded = jwt.verify(token, jwtSecret);
      console.log(decoded);
      let user = await User.findById(decoded.id);
      if (!user) {
        throw new AuthenticationError("Invalid user credentials.");
      }
      return user;
    }
    if (requireAuth) {
      throw new AuthenticationError("You must be logged in.");
    }
    return null;
  },
  issueToken: async (user) => {
    let token = "Bearer " + (await authFunctions.createToken(user));
    let refreshToken = await authFunctions.createToken(user, 100);
    return { token, refreshToken };
  },
  issueNewToken: async (req) => {
    try {
      const token = req.headers.refreshtoken;
      if (token) {
        const decoded = await jwt.verify(token, jwtRefreshTokenSecret);
        let user = await User.findById(decoded.id);
        console.log(user);
        if (!user) {
          throw new AuthenticationError("No user found.");
        }
        let tokens = await authFunctions.issueToken(user);
        return { ...tokens, user };
      }
    } catch (err) {
      throw new AuthenticationError("Invalid Refresh Token.");
    }
  },
  createToken: async ({ id, address }, expiresIn = 60) => {
    let secret = expiresIn === 60 ? jwtSecret : jwtRefreshTokenSecret;
    return await jwt.sign({ id, address }, secret, { expiresIn });
  },
};

module.exports = authFunctions;

架构:_

const { gql } = require("apollo-server");

const typeDefs = gql`
  scalar Date
  scalar MongoId

  type User {
    _id: MongoId!
    address: String!
    createdAt: Date
  }

  type Auth {
    user: User!
    token: String!
    refreshToken: String!
  }

  input LoginInput {
    address: String!
  }

  type Query {
    refreshTokens: Auth!
  }

  type Mutation {
    createOrGetUser(loginInput: LoginInput): Auth!
  }
`;

module.exports = typeDefs;

解析器:_

const User = require("../../models/User");
const {
  issueToken,
  issueNewToken,
  checkSignedIn,
} = require("../../middleware/Auth");

const resolvers = {
  Query: {
    refreshTokens: async (root, args, { req }, info) =>
      await issueNewToken(req),
  },

  Mutation: {
    createOrGetUser: async (root, args, { req }, info) => {
      try {
        const existingUser = await User.findOne({
          address: args.loginInput.address,
        })
          .populate({
            path: "orders",
            model: Order,
          })
          .exec();

        if (existingUser) {
          let tokens = await issueToken(existingUser);

          return {
            user: existingUser,
            ...tokens,
          };
        }
        const user = await new User({
          address: args.loginInput.address,
        });
        const result = await user.save();

        let tokens = await issueToken(result);

        return {
          user,
          ...tokens,
        };
      } catch (err) {
        throw err;
      }
    },
  },
};

module.exports = resolvers;

App.js

import React, { Fragment } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import {
  ApolloClient,
  ApolloLink,
  createHttpLink,
  useReactiveVar,
  concat,
} from "@apollo/client";
import { ApolloProvider } from "@apollo/client/react";
import { accessTokenVar, isLoggedInVar } from "./cache";
import cache from "./cache.js";
import jwtDecode from "jwt-decode";

// Styling
import "./styles/App.css";

// Components
import Routes from "./components/routing/Routes";
import Navbar from "./components/navbar/Navbar";
import { REFRESH_TOKENS } from "./queries/User";

const httpLink = createHttpLink({
  uri: "/graphql",
});

const refreshTokens = () => {
  return client.query({ query: REFRESH_TOKENS }).then((response) => {
    console.log(response);
    // Need to set the token to cache here, but the query doesn't get executed it seems

    //accessTokenVar(response.data)
    return;
  });
};

const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  let token = accessTokenVar();
  if (token) {
    token = token.replace("Bearer ", "");
    const { exp } = jwtDecode(token);
    console.log(exp);
    // Refresh the token a minute early to avoid latency issues
    const expirationTime = exp - 30;
    if (Date.now() / 1000 >= expirationTime) {
      refreshTokens(); // this keeps going on a loop
    }
  }

  operation.setContext(({ headers = {} }) => ({
    headers: {
      ...headers,
      authorization: token ? token : "",
    },
  }));

  return forward(operation);
});

const client = new ApolloClient({
  cache,
  link: concat(authMiddleware, httpLink),
  connectToDevTools: true,
  credentials: "include",
});

const App = () => {
  const accessToken = useReactiveVar(accessTokenVar);
  const isLoggedIn = useReactiveVar(isLoggedInVar);

  //let isLoggedIn;
  accessToken ? isLoggedInVar(true) : isLoggedInVar(false);

  return (
    <ApolloProvider client={client}>
      <Router>
        <Fragment>
          <Navbar isLoggedIn={isLoggedIn} />
        </Fragment>
      </Router>
    </ApolloProvider>
  );
};

export default App;

注意:第一次执行时createOrGetUser,流程有效。然后,我等待 30 秒并发送一个受保护的查询,之后它进入一个无限循环。

总的来说,我觉得这个流程在某种程度上被打破了,但我无法弄清楚到底是什么。对此有任何帮助将不胜感激!

标签: reactjsgraphqlapollo-client

解决方案


推荐阅读