成功登录后如何更新 ApolloClient 授权标头?

Posted

技术标签:

【中文标题】成功登录后如何更新 ApolloClient 授权标头?【英文标题】:How to update ApolloClient authorization header after successful login? 【发布时间】:2019-10-07 12:36:30 【问题描述】:

基本上,我们在index.js 文件中有这个设置ApolloProvider 授权以进行查询/突变。

import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

import ApolloClient from "apollo-boost";
import  ApolloProvider  from "react-apollo";

let session = localStorage.getItem("session");
let authorization = "";
if(session) 
    let sessionObj = JSON.parse(session);
    authorization = sessionObj.access_token


const graphQLServerURL = process.env.REACT_APP_API_URL;
const client = new ApolloClient(
    uri: graphQLServerURL + "/graphql",
    headers: 
       authorization,
    
);

ReactDOM.render(
    <ApolloProvider client=client>
        <App />
    </ApolloProvider>
    , document.getElementById('root'));

当应用首次加载时,authorization 标头将为 null。然而,在&lt;App&gt; 组件中,我们有一个&lt;Login&gt; 组件,它基本上使用usernamepassword 进行发布请求。在.then() 方法中成功请求后,我们有:

.then(res => 
if (res === 200) 
    localStorage.setItem("session", JSON.stringify(res.data));
    history.push("/dashboard");
);

所以发生的情况是用户被重定向到具有&lt;Query&gt; 组件的&lt;Dashboard&gt; 组件(列出一些数据)。但是,ApolloClient 中的 authorization 仍然是 null,直到我点击刷新。使用push 不会重新加载&lt;App&gt; 组件(因此它会从本地存储中获取更新的session)。

我应该如何做到这一点,在登录成功发布请求后,来自index.js 的授权获得最新的session 对象,而无需重新加载整个应用程序?

【问题讨论】:

Apollo Client 3 文档似乎显示了如何在每次调用中包含标头。 apollographql.com/docs/react/networking/authentication/#headerncabral 下面有答案 【参考方案1】:

如果你使用apollo-boost,你可以使用request函数

const getToken = () => 
  const token = localStorage.getItem('token');
  return token ? `Bearer $token` : '';
;

const client = new ApolloClient(
  uri: `$graphQLServerURL/graphql`,
  request: (operation) => 
    operation.setContext(
      headers: 
        authorization: getToken(),
      ,
    );
  ,
);

【讨论】:

如果您使用的是 Apollo Client 3,您应该查看以下 ncabral 使用 Apollo Client 的 setContext 提供的答案。最新的 Apollo Client API 文档中引用了该答案(请参阅 ncabral 答案中的 REF 链接)。【参考方案2】:

您必须使用链接。 Ref

const httpLink = createHttpLink(
  uri: '/graphql',
);

const authLink = setContext((_,  headers ) => 
  // get the authentication token from local storage if it exists
  const token = localStorage.getItem('token');
  // return the headers to the context so httpLink can read them
  return 
    headers: 
      ...headers,
      authorization: token ? `Bearer $token` : "",
    
  
);

const client = new ApolloClient(
  link: authLink.concat(httpLink),
);

【讨论】:

但是,我们的应用正在使用来自apollo-boostApolloClient,它无法识别link 选项。 当,希望这不会被否决。对于新的 Apollo Client 3,这是首选路径,可以节省我的时间。由于投反对票,完全跳过了这个答案>.> @afreeland 同意,这个答案被严重低估了。【参考方案3】:

我遇到了类似的问题。我想将token 用于ApolloProvider。当应用程序第一次渲染时,我无法访问localStorage,因此 WebSocket 无法工作。

我做了什么?

我创建了一个自定义挂钩并使用useEffectuseState 来更新它。


import 
  ApolloClient, InMemoryCache, HttpLink, split,
 from '@apollo/client';
import  WebSocketLink  from 'apollo-link-ws';
import  getMainDefinition  from 'apollo-utilities';
import  useEffect, useState  from 'react';
import config from './index';

export const useConfigClient = () => 
  const [authTokenStorage, setAuthTokenStorage] = useState(localStorage.getItem('token'));   // by default it is null

  const cache = new InMemoryCache();

  const httpLink = new HttpLink(
    uri: config.GRAPHQL_URL,
    headers: 
      authorization: `Bearer $authTokenStorage`,
    ,
  );

  const wsLink = new WebSocketLink(
    uri: config.GRAPHQL_WS,
    options: 
      reconnect: true,
      connectionParams: 
        headers: 
          authorization: `Bearer $authTokenStorage`,
        ,
      ,
    ,
  );

  const link = split(
    ( query ) => 
      const definition = getMainDefinition(query);
      return (
        definition.kind === 'OperationDefinition'
        && definition.operation === 'subscription'
      );
    ,
    wsLink,
    httpLink,
  );

  useEffect(() => 
    setAuthTokenStorage(localStorage.getItem('token')); // as soon as it is available just update the token
  , []);

  const client = new ApolloClient(
    cache,
    link,
  );

  return client;
;

我是如何使用自定义挂钩的?


import  ApolloProvider  from '@apollo/client';
import  useConfigClient  from '../config/apolloConfig';  // import the hooks

<ApolloProvider client=useConfigClient()>
  //  ... add your component
</ ApolloProvider>

我不确定这是修复它的最佳方法。目前,它有效,我希望它能对其他人有所帮助。

【讨论】:

当我使用这个时,我得到一个无效的 Hook 错误。 “钩子只能在函数组件的主体内部调用。”这是有道理的,因为这里使用的函数不是组件。【参考方案4】:

实现此目的的另一种方法是您可以使用 apollo-config 中的 setLink 方法。 之后,导出一个函数,一旦你有你的令牌就会设置标题。

import ApolloClient, InMemoryCache, HttpLink from '@apollo/client';
import setContext from '@apollo/client/link/context';

const httpLink = new HttpLink(uri: '/graphql');

let config = new ApolloClient(
  link: httpLink,
  cache: new InMemoryCache(),
);

export function setGraphqlHeaders() 
  const token = localStorage.getItem(AUTH_TOKEN);
  const authLink = setContext((_, headers) => 
    return 
      headers: 
        ...headers,
        authorization: token || null,
      ,
    ;
  );

  config.setLink(authLink.concat(httpLink));

希望,这会对某人有所帮助

【讨论】:

【参考方案5】:

praveen-me 的答案在 2021 年对我有用 Nextjs。我尝试了ncabral 的答案,但我不确定它会自动为我更新 JWT。

praveen-me 的回答之上,我为我在代码中添加了一些内容:

userToken.ts

const userTokenKey = 'userToken'

export const getUserToken = () => 
    try 
        return localStorage.getItem(userTokenKey)   
     catch (error) 
        ...
    


export const setUserToken = (token: string) => 
    try 
        localStorage.setItem(userTokenKey, token)
     catch (error) 
        ...
    


apolloClient.ts

import  ApolloClient, createHttpLink, InMemoryCache  from "@apollo/client";
import  setContext  from "@apollo/client/link/context";
import  getUserToken  from "./userToken";

const httpLink = createHttpLink(
    uri: process.env.NEXT_PUBLIC_GRAPHQL_URL
)

let client = new ApolloClient(
    // link: authLink.concat(httpLink),
    link: httpLink,
    cache: new InMemoryCache(),
);

export function updateGraphqlHeaders() 
    const authLink = setContext((_,  headers ) => 
        const token = getUserToken();
        return 
            headers: 
                ...headers,
                authorization: token ? `Bearer $token` : "",
            
        
    )
    client.setLink(authLink.concat(httpLink))

updateGraphqlHeaders() // Init

export default client;

_app.tsx

...
import  ApolloProvider  from "@apollo/client";
import client from "../auth/apolloClient";

function MyApp( Component, pageProps : AppProps) 
  return (
    <ApolloProvider client=client>
      <Component ...pageProps />
    </ApolloProvider>
  );


export default MyApp;

【讨论】:

以上是关于成功登录后如何更新 ApolloClient 授权标头?的主要内容,如果未能解决你的问题,请参考以下文章

微信授权登录(更新。。。)

ApolloClient:optimisticResponse 在更新期间变为“未定义”

使用 Spring Security 成功登录后如何正确更新登录日期时间?

使用 Spring Security 成功登录后如何正确更新登录日期时间?

登录状态不会在 React、Apollo Client 2、Graphcool 中被动更新

成功登录后如何更新 Spring Security UserDetails impls?