How do I get a token outside of useAuth
with @clerk/expo
#1751
-
I'm trying to add an auth header to my fetch function but I'm finding it difficult.
|
Beta Was this translation helpful? Give feedback.
Replies: 16 comments 13 replies
-
I'm not a Clerk expert but I ran into a similar issue (with react not expo). My workaround was to manually instantiate a I'm really not sure if this is best practice. Their examples show that if you can do things using custom hooks you should do that -- that approach doesn't work in my case but maybe it does for you. Also, it appears that Clerk sets a global Not sure if any of this helps, but figured I'd ping since I ran into a very similar issue and found your post while looking for solutions. |
Beta Was this translation helpful? Give feedback.
-
@JonLoesch It helps. I figured out how to achieve the API I wanted using hooks. I asked on Discord but didn't get a straight answer. I also dug through the source code and found the same things. I was going to do something similar but went with the hooks route. It would seem like they expect you to repeat a bunch of logic on every API call or revert to some awkward API where you pass in the const Component = () => {
const {getToken} = useAuth();
useEffect(() => {
authApi(getToken, 'users')
}, [])
return (
<div />
)
}
const authApi = async (getToken, url) => {
const token = await getToken()
return fetch(url, {headers: {Authorization: token}})
} Doing the above was a poor DX to me, and since I'm using react query, it didn't solve the issue. export const api = ky.create({
prefixUrl: env.apiUrl,
hooks: { beforeRequest: [printRequest], afterResponse: [printResponse] },
});
export type Api = typeof api;
export type GetToken = () => Promise<string | null>;
const createAuthApi = async (tokenGetter: GetToken) => {
const token = await tokenGetter();
if (!token) throw new Error('User is not signed in');
return api.extend({ headers: { Authorization: `Bearer ${token}` } });
};
type AuthQueryFunction<
T = unknown,
TQueryKey extends QueryKey = QueryKey,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TPageParam = any,
> = (api: Api, context: QueryFunctionContext<TQueryKey, TPageParam>) => T | Promise<T>;
export function useAuthQuery<
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>({
authQueryFn,
...options
}: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
authQueryFn: AuthQueryFunction<TQueryFnData, TQueryKey, unknown>;
}): UseQueryResult<TData, TError> {
const { getToken } = useAuth();
return useQuery({
...options,
queryFn: async (args) => {
const api = await createAuthApi(getToken);
// I pass the created api with the auth header already setup to the caller
return authQueryFn(api, args);
},
});
} and using that looks like export const useUsers = () => {
const { data, ...rest } = useAuthQuery({
queryKey: ['users'],
authQueryFn: (api) => api.get(`users`).json<User[]>()
});
return { users: data, ...rest }; It's disappointing and frustrating that it seemed like there was no acknowledgment of a problem here when I asked on Discord. I'll give them the benefit of the doubt since it seemed like me and the clerk dev weren't communicating that well. The issue still remains, though: the fact that developers can't encapsulate |
Beta Was this translation helpful? Give feedback.
-
hello @nvsd and thank you for opening this issue. I think you could also try the following solutions: // 1. Using hook exported from the @clerk/expo
import { useClerk } from '@clerk/expo';
const clerk = useClerk();
const token = await clerk.session?.getToken();
fetch('whatever', {headers: {Authorization: token })
// 2. initializing a new clerk client using @clerk/clerk-js
import Clerk from '@clerk/clerk-js/headless';
const clerk = new Clerk(publishableKey);
const token = await Clerk.session?.getToken();
fetch('whatever', {headers: {Authorization: token }) |
Beta Was this translation helpful? Give feedback.
-
@nvsd Glad you got something working, and it sounds like you've already solved the problem, even if you're not 100% happy with the solution. I'm going to try to flesh out my idea a bit more because I think it may be what you're looking for? As I've understood it, it sounds like you want your DX to be something along the lines of a globally available
And then wherever you have your
This way both requests going through your api logic and requests coming from Clerk built in components (i.e. going through the react context) will be using the same underlying Clerk object. I think you'll still need to call |
Beta Was this translation helpful? Give feedback.
-
P.S. This is not clerk related but maybe still worth mentioning? I said you needed to call For example, I'm using in a trpc application which has this for custom headers:
The fact that it offers |
Beta Was this translation helpful? Give feedback.
-
I have the same issue using @clerk/clerk-react. I'd love some kind of official way to retrieve a token outside of the react context with the react library |
Beta Was this translation helpful? Give feedback.
-
Hi! We also have the same issue. I would love a way to subscribe to a valid token, that would just renew automatically before expired or null if logged out, this would solve alot of issues export default function TokenWindowProvider({children}) {
const clerk = useClerk();
const token = await clerk.session?.subscribeToToken(token => {
window.clerkToken = token;
});
return children
} |
Beta Was this translation helpful? Give feedback.
-
I know this is an old issue, but definitely a sore point for us as well using Clerk. Has anyone found a seamless solution to use here? |
Beta Was this translation helpful? Give feedback.
-
this is the solution I implement on expo context/graphql/GraphQLClientContext.tsx // GraphQLClientContext.tsx
import React from "react";
import { GraphQLClient } from "graphql-request";
// Create a context with an undefined GraphQLClient by default
const GraphQLClientContext = React.createContext<GraphQLClient | undefined>(
undefined
);
export default GraphQLClientContext; context/graphql/GraphQLClientProvider.tsx import React, { useEffect, useState } from "react";
import GraphQLClientContext from "./GraphQLClientContext";
import { useAuth } from "@clerk/clerk-expo";
import { getGqlClient } from "../../gql-client";
import { GraphQLClient } from "graphql-request";
export const GraphQLClientProvider: React.FC<{
children: React.JSX.Element;
}> = ({ children }) => {
const { getToken } = useAuth();
const [client, setClient] = useState<GraphQLClient>();
useEffect(() => {
const initClient = async () => {
const token = await getToken({ template: "hasura" });
if (!token) return;
const newClient = getGqlClient(token);
setClient(newClient);
};
initClient();
}, [getToken]);
if (!client) return null; // Optionally, render a loading spinner or a similar indicator
return (
<GraphQLClientContext.Provider value={client}>
{children}
</GraphQLClientContext.Provider>
);
}; hooks/useGraphQLClient.ts import { useContext } from "react";
import GraphQLClientContext from "../context/graphql/GraphQLClientContext";
const useGraphQLClient = () => {
const client = useContext(GraphQLClientContext);
if (!client) {
throw new Error(
"useGraphQLClient must be used within a GraphQLClientProvider"
);
}
return client;
};
export default useGraphQLClient; finally App.tsx
|
Beta Was this translation helpful? Give feedback.
-
After going round in circles for hours, I think I've got something working. I'm using Here's an example of how I use it to add the bearer token in an Axios interceptor. This means I can use vanilla react-query without any extra boiler plate.
|
Beta Was this translation helpful? Give feedback.
-
I didn't want to add expo, but if you have access to clerk you can add listeners that are called whenever user or session are updated:
|
Beta Was this translation helpful? Give feedback.
-
We've opened #3420 in order to address the issue |
Beta Was this translation helpful? Give feedback.
-
I just looked at the PR, this is exactly what I need, but I need it in clerk-react. Can you add it there too? |
Beta Was this translation helpful? Give feedback.
-
Saw this message in Discord by @royanger, and I solved with: type WindowWithClerk = Window & {
Clerk?: {
session?: {
getToken(): Promise<string | null>
}
}
}
export const getSessionToken = async () => {
if (!(window as WindowWithClerk).Clerk?.session) return null
return (await (window as WindowWithClerk)?.Clerk?.session?.getToken()) ?? null
} |
Beta Was this translation helpful? Give feedback.
-
Closing this discussion finally after quite a long run - we have official solutions for this issue for both expo and browser-based SDKs linked in the comments:
🙌 |
Beta Was this translation helpful? Give feedback.
-
For those who arrive on this thread but use clerk AND expo not with You can import import { Clerk } from "@clerk/clerk-expo";
export const myAsyncFunc = async (foo: Bar): Promise<any> => {
const token = await Clerk.session?.getToken();
// ...
} |
Beta Was this translation helpful? Give feedback.
We've opened #3420 in order to address the issue