简体   繁体   English

来自 Gatsby 中异步 getIdTokenClaims() 的 JWT GraphQL 查询

[英]GraphQL query with JWT from async getIdTokenClaims() in Gatsby

Updated更新

As @shahroon-farooqi demonstrated below this pattern is well documented by urql here including rationale for the wonka library dependency .正如@shahroon-farooqi 下面演示的那样,urql 在这里很好地记录了这种模式,包括wonka 库依赖的基本原理。

Updated working demo on GitHub .更新了 GitHub 上的工作演示。


I'm new to React and still having trouble working out how the async awaits fit together with respect to hooks.我是 React 的新手,仍然无法弄清楚异步等待如何与钩子结合在一起。

I have a minimal Gatsby/React starter project (my full code on GitHub here ) that I have pieced together from other examples that:我有一个最小的 Gatsby/React 入门项目(我在 GitHub 上完整代码),我从其他示例拼凑而成:

  1. Signs-in and gets a JWT from Auth0 and saves it in local storage;登录并从 Auth0 获取 JWT 并将其保存在本地存储中; and then接着
  2. Includes that JWT in a GraphQL request to fetch and display a list of Organizations.在 GraphQL 请求中包含该 JWT,以获取和显示组织列表。

When this is processed in the browser as 2 separate page loads (ie I first click sign-in and I'm then redirected to the view the list of Organizations page) it works as expected.当这在浏览器中作为 2 个单独的页面加载处理时(即我首先单击登录,然后我被重定向到查看组织页面列表),它按预期工作。 But once I'm already signed-in and I click the browser refresh button on the list of Organizations page, I can see that GraphQL fetch fails because it is called before the client has had a chance to load the JWT into the header.但是,一旦我已经登录并单击组织页面列表上的浏览器刷新按钮,我可以看到 GraphQL 提取失败,因为它在客户端有机会将 JWT 加载到标头中之前被调用。 The JWT is successfully loaded a split second later but I need this to happen before the GraphQL fetch is attempted. JWT 稍后会成功加载,但我需要在尝试 GraphQL 提取之前发生这种情况。

The auth and client is passed as a wrapper:身份验证和客户端作为包装器传递:

// gatsby-browser.js    
export const wrapRootElement = ({ element }) => {
  return (
    <Auth0Provider
      domain={process.env.GATSBY_AUTH0_DOMAIN}
      redirectUri={process.env.GATSBY_AUTH0_REDIRECT_URI}
      ...
    >
      <AuthApolloProvider>{element}</AuthApolloProvider>
    </Auth0Provider>
  );
}

And the client is set up with the JWT like this:客户端是这样设置 JWT 的:

// src/api/AuthApolloProvider.tsx
const setupClient = (_token) => {
  return createClient({
    url: process.env.GATSBY_HASURA_GRAPHQL_URL,
    fetchOptions: () => {
      return {
        headers: {
          authorization: _token ? `Bearer ${_token}` : "",
        },
      };
    },
  });
};
const AuthApolloProvider = ({ children }) => {
  const { getAccessTokenSilently, isAuthenticated, getIdTokenClaims } =
    useAuth0();
  const [token, setToken] = useState("");
  const [client, setClient] = useState(setupClient(token));
  useEffect(() => {
    (async () => {
      if (isAuthenticated) {
        const tokenClaims = await getIdTokenClaims();
        setToken(tokenClaims.__raw);
      }
    })();
  }, [isAuthenticated, getAccessTokenSilently]);
  useEffect(() => {
    setClient(setupClient(token));
  }, [token]);
  return <Provider value={client}>{children}</Provider>;
};

I then have this contoller to get the list of Organizations:然后我有这个控制器来获取组织列表:

// src/controllers/Organizations.ts
import { useQuery } from "urql"

const GET_ORGANIZATIONS = `
query get_organizations {
  organizations {
    name
    label
  }
}
`
export const useOrganizations = () => {
  const [{ data, fetching }] = useQuery({ query: GET_ORGANIZATIONS })
  return {
    organizations: data?.organizations,
    loading: fetching,
  }
}

And finally my list of Organizations component:最后是我的组织组件列表:

// src/components/Organizations/OrganizationList.tsx
import { useOrganizations } from "../../controllers/Organizations";

const OrganizationList = () => {
  const { organizations, loading } = useOrganizations();
  return (
    <>
      {loading ? (
        <p>Loading...</p>
      ) : (
        organizations.map((organization: OrganizationItemType) => (
          <OrganizationItem
            organization={organization}
            key={organization.name}
          />
        ))
      )}
    </>
  );
};

So as I understand it, I don't want to make the useOrganizations() call in the component until the async method inside AuthApolloProvider has completed and successfully loaded the JWT into the client.据我了解,在AuthApolloProvider内部的异步方法完成并将 JWT 成功加载到客户端之前,我不想在组件中进行useOrganizations()调用。

Because I'm new to React and I've pieced this together from other examples I'm not sure how to approach this - any help would be great.因为我是 React 的新手,并且我已经从其他示例中拼凑起来,所以我不确定如何处理这个问题 - 任何帮助都会很棒。

  useEffect(() => {
    if (token.length === 0) return
    setClient(setupClient(token))
  }, [token])

You probably don't want to setClient/setupClient when token is empty?当令牌为空时,您可能不想设置客户端/设置客户端? Code inside useEffect gets executed at least 2 time. useEffect 中的代码至少执行 2 次。

  1. Component mount (at this point token is still empty)组件挂载(此时令牌仍为空)
  2. When token value gets changed当令牌值改变时

I have modified your ApolloProvider.我已经修改了你的 ApolloProvider。 You will need to add Wonka, as urql utilises the Wonka library.您需要添加 Wonka,因为 urql 使用 Wonka 库。

import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { pipe, map, mergeMap, fromPromise, fromValue } from "wonka";
import {
  createClient,
  Provider,
  dedupExchange,
  cacheExchange,
  fetchExchange,
  Exchange,
  Operation,
} from "urql";

interface AuthApolloProviderProps {
  children: React.ReactChildren;
}

const fetchOptionsExchange =
  (fn: any): Exchange =>
  ({ forward }) =>
  (ops$) => {
    return pipe(
      ops$,
      mergeMap((operation: Operation) => {
        const result = fn(operation.context.fetchOptions);
        return pipe(
          (typeof result.then === "function"
            ? fromPromise(result)
            : fromValue(result)) as any,
          map((fetchOptions: RequestInit | (() => RequestInit)) => ({
            ...operation,
            context: { ...operation.context, fetchOptions },
          }))
        );
      }),
      forward
    );
  };

const AuthApolloProvider = ({ children }: AuthApolloProviderProps) => {
  const { getAccessTokenSilently, getIdTokenClaims } = useAuth0();

  const url = process.env.GATSBY_HASURA_GRAPHQL_URL;
  let client = null;

  if (url) {
    client = createClient({
      url: url,
      exchanges: [
        dedupExchange,
        cacheExchange,
        fetchOptionsExchange(async (fetchOptions: any) => {
          await getAccessTokenSilently({
            audience: process.env.GATSBY_AUTH0_AUDIENCE,
            scope: "openid profile email offline_access",
            ignoreCache: true,
          });

          const tokenClaims = await getIdTokenClaims();
          const token = tokenClaims?.__raw;

          return Promise.resolve({
            ...fetchOptions,
            headers: {
              Authorization: token ? `Bearer ${token}` : "",
            },
          });
        }),
        fetchExchange,
      ],
    });
  } else {
    throw new Error("url not define");
  }

  return <Provider value={client}>{children}</Provider>;
};

export default AuthApolloProvider;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM