因此,我们正在使用Apollo和GraphQL创建一个React-Native应用程序。我正在使用基于JWT的身份验证(当用户同时登录 activeToken 和 refreshToken 时均已创建),并且想要实现一个流程,当服务器注意到令牌已过期时,令牌会自动刷新。
Apollo链接错误的Apollo文档提供了很好的starting point来捕获ApolloClient的错误:
export REPO="wordpress-sites"
但是,我真的很难弄清楚如何实现 getNewToken()。 我的GraphQL端点具有用于创建新令牌的解析器,但是我无法从Apollo-Link-Error调用它吗?
如果令牌是在Apollo客户端将连接到的GraphQL端点中创建的,那么如何刷新令牌?
答案 0 :(得分:9)
Apollo错误链接文档中给出的example是一个很好的起点,但是假设 getNewToken()操作是同步的。 对于您的情况,您必须点击GraphQL端点才能获取新的访问令牌。这是异步操作,您必须使用apollo-link包中的 fromPromise 实用程序功能将Promise转换为Observable。
import React from "react";
import { AppRegistry } from 'react-native';
import { onError } from "apollo-link-error";
import { fromPromise, ApolloLink } from "apollo-link";
import { ApolloClient } from "apollo-client";
let apolloClient;
const getNewToken = () => {
return apolloClient.query({ query: GET_TOKEN_QUERY }).then((response) => {
// extract your accessToken from your response data and return it
const { accessToken } = response.data;
return accessToken;
});
};
const errorLink = onError(
({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
case "UNAUTHENTICATED":
return fromPromise(
getNewToken().catch((error) => {
// Handle token refresh errors e.g clear stored tokens, redirect to login
return;
})
)
.filter((value) => Boolean(value))
.flatMap((accessToken) => {
const oldHeaders = operation.getContext().headers;
// modify the operation context with a new token
operation.setContext({
headers: {
...oldHeaders,
authorization: `Bearer ${accessToken}`,
},
});
// retry the request, returning the new observable
return forward(operation);
});
}
}
}
}
);
apolloClient = new ApolloClient({
link: ApolloLink.from([errorLink, authLink, httpLink]),
});
const App = () => (
<ApolloProvider client={apolloClient}>
<MyRootComponent />
</ApolloProvider>
);
AppRegistry.registerComponent('MyApplication', () => App);
您可以停止上面的实现,直到两个或多个请求同时失败为止。因此,要处理令牌到期时的并发请求失败,请查看此post
答案 1 :(得分:0)
如果您使用的是 JWT,您应该能够检测到您的 JWT 令牌何时即将过期或是否已经过期。
因此,您无需发出总是因 401 未授权而失败的请求。
您可以通过以下方式简化实现:
const REFRESH_TOKEN_LEGROOM = 5 * 60
export function getTokenState(token?: string | null) {
if (!token) {
return { valid: false, needRefresh: true }
}
const decoded = decode(token)
if (!decoded) {
return { valid: false, needRefresh: true }
} else if (decoded.exp && (timestamp() + REFRESH_TOKEN_LEGROOM) > decoded.exp) {
return { valid: true, needRefresh: true }
} else {
return { valid: true, needRefresh: false }
}
}
export let apolloClient : ApolloClient<NormalizedCacheObject>
const refreshAuthToken = async () => {
return apolloClient.mutate({
mutation: gql```
query refreshAuthToken {
refreshAuthToken {
value
}```,
}).then((res) => {
const newAccessToken = res.data?.refreshAuthToken?.value
localStorage.setString('accessToken', newAccessToken);
return newAccessToken
})
}
const apolloHttpLink = createHttpLink({
uri: Config.graphqlUrl
})
const apolloAuthLink = setContext(async (request, { headers }) => {
// set token as refreshToken for refreshing token request
if (request.operationName === 'refreshAuthToken') {
let refreshToken = localStorage.getString("refreshToken")
if (refreshToken) {
return {
headers: {
...headers,
authorization: `Bearer ${refreshToken}`,
}
}
} else {
return { headers }
}
}
let token = localStorage.getString("accessToken")
const tokenState = getTokenState(token)
if (token && tokenState.needRefresh) {
const refreshPromise = refreshAuthToken()
if (tokenState.valid === false) {
token = await refreshPromise
}
}
if (token) {
return {
headers: {
...headers,
authorization: `Bearer ${token}`,
}
}
} else {
return { headers }
}
})
apolloClient = new ApolloClient({
link: apolloAuthLink.concat(apolloHttpLink),
cache: new InMemoryCache()
})
这种实现的优点:
缺点: