从 Gatsby 中的异步 getIdTokenClaims() 使用 JWT 进行 GraphQL 查询
GraphQL query with JWT from async getIdTokenClaims() in Gatsby
已更新
如@shahroon-farooqi 所示,此模式在 urql here including rationale for the wonka library dependency.
中有详细记录
更新了工作演示 on GitHub。
我是 React 的新手,仍然无法弄清楚异步等待如何与挂钩相结合。
我有一个最小的 Gatsby/React 启动项目(我在 GitHub here 上的完整代码)是我从其他示例拼凑而成的:
- Signs-in 并从 Auth0 获取一个 JWT 并保存在本地存储中;然后
- 在 GraphQL 请求中包含该 JWT 以获取和显示组织列表。
当这在浏览器中作为 2 个单独的页面加载进行处理时(即我首先单击 sign-in 然后我被重定向到查看组织列表页面)它按预期工作。但是一旦我已经 signed-in 并单击组织页面列表上的浏览器刷新按钮,我可以看到 GraphQL 提取失败,因为它在客户端有机会将 JWT 加载到 header。 JWT 稍后成功加载,但我需要在尝试 GraphQL 提取之前发生。
身份验证和客户端作为包装器传递:
// 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>
);
}
客户端是这样设置 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>;
};
然后我有这个控制器来获取组织列表:
// 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,
}
}
最后是我的组织组件列表:
// 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}
/>
))
)}
</>
);
};
据我了解,在 AuthApolloProvider
中的异步方法完成并成功将 JWT 加载到客户端之前,我不想在组件中进行 useOrganizations()
调用。
因为我是 React 的新手,而且我是从其他示例中拼凑出来的,所以我不确定如何处理这个问题 - 任何帮助都会很棒。
useEffect(() => {
if (token.length === 0) return
setClient(setupClient(token))
}, [token])
当令牌为空时,您可能不想setClient/setupClient? useEffect 中的代码至少执行 2 次。
- 组件挂载(此时token还是空的)
- 令牌值更改时
我修改了你的ApolloProvider。您需要添加 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;
已更新
如@shahroon-farooqi 所示,此模式在 urql here including rationale for the wonka library dependency.
中有详细记录更新了工作演示 on GitHub。
我是 React 的新手,仍然无法弄清楚异步等待如何与挂钩相结合。
我有一个最小的 Gatsby/React 启动项目(我在 GitHub here 上的完整代码)是我从其他示例拼凑而成的:
- Signs-in 并从 Auth0 获取一个 JWT 并保存在本地存储中;然后
- 在 GraphQL 请求中包含该 JWT 以获取和显示组织列表。
当这在浏览器中作为 2 个单独的页面加载进行处理时(即我首先单击 sign-in 然后我被重定向到查看组织列表页面)它按预期工作。但是一旦我已经 signed-in 并单击组织页面列表上的浏览器刷新按钮,我可以看到 GraphQL 提取失败,因为它在客户端有机会将 JWT 加载到 header。 JWT 稍后成功加载,但我需要在尝试 GraphQL 提取之前发生。
身份验证和客户端作为包装器传递:
// 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>
);
}
客户端是这样设置 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>;
};
然后我有这个控制器来获取组织列表:
// 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,
}
}
最后是我的组织组件列表:
// 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}
/>
))
)}
</>
);
};
据我了解,在 AuthApolloProvider
中的异步方法完成并成功将 JWT 加载到客户端之前,我不想在组件中进行 useOrganizations()
调用。
因为我是 React 的新手,而且我是从其他示例中拼凑出来的,所以我不确定如何处理这个问题 - 任何帮助都会很棒。
useEffect(() => {
if (token.length === 0) return
setClient(setupClient(token))
}, [token])
当令牌为空时,您可能不想setClient/setupClient? useEffect 中的代码至少执行 2 次。
- 组件挂载(此时token还是空的)
- 令牌值更改时
我修改了你的ApolloProvider。您需要添加 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;