T
TanStack3y ago
fair-rose

How to work with rotating auth tokens? (Clerk)

I have a React context, that wraps my entire application. In this context, I have implemented a useQuery hook that fetches some global settings data for my application. However, updating other things in my application has the potential to invalidate the cache that this call has, and so, it will need to be refetched (which happens automatically, since we are dealing with a global context). Here is what I think might be problem. I think useQuery might be caching (?) the auth token that is passed to my backend in the call? Code:
const GlobalDataContext = createContext<GlobalData>(undefined!);

function GlobalDataProvider({ children }: PropsWithChildren) {
const { getToken } = useAuth();
const [clerkToken, setClerkToken] = useState<string | null>(null);

const generateToken = async () => {
const token = await getToken();
setClerkToken(token);
};

useEffect(() => {
generateToken();
}, []);

const { isLoading, error, data } = useQuery({
queryKey: ["organizationData"],
queryFn: () =>
fetch(`${getApiUrl()}/api/v1/organization`, {
method: "GET",
headers: {
Authorization: `Bearer ${clerkToken}`,
},
}).then((res) => res.json()),
refetchOnWindowFocus: false,
enabled: !!clerkToken,
staleTime: Infinity,
cacheTime: Infinity,
});

return (
<GlobalDataContext.Provider
value={{
organizationSettings: data,
generateToken
}}
>
{children}
</GlobalDataContext.Provider>
);
}
const GlobalDataContext = createContext<GlobalData>(undefined!);

function GlobalDataProvider({ children }: PropsWithChildren) {
const { getToken } = useAuth();
const [clerkToken, setClerkToken] = useState<string | null>(null);

const generateToken = async () => {
const token = await getToken();
setClerkToken(token);
};

useEffect(() => {
generateToken();
}, []);

const { isLoading, error, data } = useQuery({
queryKey: ["organizationData"],
queryFn: () =>
fetch(`${getApiUrl()}/api/v1/organization`, {
method: "GET",
headers: {
Authorization: `Bearer ${clerkToken}`,
},
}).then((res) => res.json()),
refetchOnWindowFocus: false,
enabled: !!clerkToken,
staleTime: Infinity,
cacheTime: Infinity,
});

return (
<GlobalDataContext.Provider
value={{
organizationSettings: data,
generateToken
}}
>
{children}
</GlobalDataContext.Provider>
);
}
Now, since Clerk's getToken is a promise, I had to do some round-about async things here, to retrieve the token. This works on page load, but after Clerk refreshes the token every 60 seconds, the one passed in this call becomes invalid. I tried exposing the generateToken that I created here, in hopes that calling this before I invalidate the cache, I would have a fresh token in my request - but this does not work. Anyone have any idea how to spin this?
3 Replies
fair-rose
fair-roseOP3y ago
If I log the clerkToken state before I invalidate the cache (after the token has been refreshed) - it appears that I do have my the updated token in my state; That is why I think the original token (generated at page load) might be cached here?
helpful-purple
helpful-purple3y ago
useEffect(() => {
generateToken();
}, []);
useEffect(() => {
generateToken();
}, []);
This runs only once upon loading the page.
const { isLoading, error, data } = useQuery({
queryKey: ["organizationData"],
queryFn: async() => {
const token = await getToken();
await fetch(`${getApiUrl()}/api/v1/organization`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
}).then((res) => res.json())
},
refetchOnWindowFocus: false,
enabled: !!clerkToken,
staleTime: Infinity,
cacheTime: Infinity,
});
const { isLoading, error, data } = useQuery({
queryKey: ["organizationData"],
queryFn: async() => {
const token = await getToken();
await fetch(`${getApiUrl()}/api/v1/organization`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
}).then((res) => res.json())
},
refetchOnWindowFocus: false,
enabled: !!clerkToken,
staleTime: Infinity,
cacheTime: Infinity,
});
Why not async & await the queryFn so that you can get the refreshed token every time this query runs?
const GlobalDataContext = createContext<GlobalData>(undefined!);

function GlobalDataProvider({ children }: PropsWithChildren) {
const { getToken } = useAuth();

const { isLoading, error, data } = useQuery({
queryKey: ["organizationData"],
queryFn: async() => {
const token = await getToken();
await fetch(`${getApiUrl()}/api/v1/organization`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
}).then((res) => res.json())
},
refetchOnWindowFocus: false,
enabled: !!clerkToken,
staleTime: Infinity,
cacheTime: Infinity,
});

return (
<GlobalDataContext.Provider
value={{
organizationSettings: data

}}
>
{children}
</GlobalDataContext.Provider>
);
}
const GlobalDataContext = createContext<GlobalData>(undefined!);

function GlobalDataProvider({ children }: PropsWithChildren) {
const { getToken } = useAuth();

const { isLoading, error, data } = useQuery({
queryKey: ["organizationData"],
queryFn: async() => {
const token = await getToken();
await fetch(`${getApiUrl()}/api/v1/organization`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
}).then((res) => res.json())
},
refetchOnWindowFocus: false,
enabled: !!clerkToken,
staleTime: Infinity,
cacheTime: Infinity,
});

return (
<GlobalDataContext.Provider
value={{
organizationSettings: data

}}
>
{children}
</GlobalDataContext.Provider>
);
}
something like this?
fair-rose
fair-roseOP3y ago
@Bharat Ah. I may have overlooked something basic in the docs: I had no idea that you are allowed to declare the queryFn as async. Thanks! This seems to work 👍

Did you find this page helpful?