You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
How do I get a token outside of useAuth with @clerk/expo #1511
I'm not a Clerk expert but I ran into a similar issue (with react not expo). My workaround was to manually instantiate a Clerk instance (had to import @clerk/clerk-js directly) and then have it exported from some util module, globally available. Then it was a simple matter of getting the token from the global object (see here). Judging from my brief look through the source, a singleton object is what the <ClerkProvider> does anyway -- but if you construct it yourself and pass it in via the Clerk prop, then you have direct access to the clerk object outside of the react tree.
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 window.Clerk reference to the singleton object ... so maybe that's the recommended approach to getting global imperative access? idk, I just am more comfortable with controlling initialization myself so I don't have to worry about order of operations when it comes to initializing clerk and my fetch middleware.
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.
@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 getToken function to another function.
Doing the above was a poor DX to me, and since I'm using react query, it didn't solve the issue.
I had to do a lot of work to make a semi-decent DX for the rest of the devs on my team but ultimately ended up with
exportconstapi=ky.create({prefixUrl: env.apiUrl,hooks: {beforeRequest: [printRequest],afterResponse: [printResponse]},});exporttypeApi=typeofapi;exporttypeGetToken=()=>Promise<string|null>;constcreateAuthApi=async(tokenGetter: GetToken)=>{consttoken=awaittokenGetter();if(!token)thrownewError('User is not signed in');returnapi.extend({headers: {Authorization: `Bearer ${token}`}});};typeAuthQueryFunction<T=unknown,TQueryKeyextendsQueryKey=QueryKey,// eslint-disable-next-line @typescript-eslint/no-explicit-anyTPageParam=any,>=(api: Api,context: QueryFunctionContext<TQueryKey,TPageParam>)=>T|Promise<T>;exportfunctionuseAuthQuery<TQueryFnData=unknown,TError=unknown,TData=TQueryFnData,TQueryKeyextendsQueryKey=QueryKey,>({
authQueryFn,
...options}: UseQueryOptions<TQueryFnData,TError,TData,TQueryKey>&{authQueryFn: AuthQueryFunction<TQueryFnData,TQueryKey,unknown>;}): UseQueryResult<TData,TError>{const{ getToken }=useAuth();returnuseQuery({
...options,queryFn: async(args)=>{constapi=awaitcreateAuthApi(getToken);// I pass the created api with the auth header already setup to the callerreturnauthQueryFn(api,args);},});}
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 await getToken inside the function you use to fetch makes API design so awkward.
hello @nvsd and thank you for opening this issue.
The Clerk from import {Clerk} from @clerk/expo is being populated when ClerkProvider is being loaded.
I think that using the above code inside a component (inside ClerkProvider), it will be populated.
I think you could also try the following solutions:
// 1. Using hook exported from the @clerk/expoimport{useClerk}from'@clerk/expo';constclerk=useClerk();consttoken=awaitclerk.session?.getToken();fetch('whatever',{headers: {Authorization: token})// 2. initializing a new clerk client using @clerk/clerk-jsimportClerkfrom'@clerk/clerk-js/headless';constclerk=newClerk(publishableKey);consttoken=awaitClerk.session?.getToken();fetch('whatever',{headers: {Authorization: token})
@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 api, rather than as a parameter in a hook callback. Assuming I have understood that correctly, then what you could have is something like (This is basically a re-working of existing code on this thread):
import Clerk from '@clerk/clerk-js/headless';
export const _api = ky.create({
prefixUrl: env.apiUrl,
hooks: { beforeRequest: [printRequest], afterResponse: [printResponse] },
});
export type Api = typeof _api;
export const clerk = new Clerk(publishableKey);
export const api = async () => {
const token = await clerk.session?.getToken();
if (!token) throw new Error('User is not signed in');
return api.extend({ headers: { Authorization: `Bearer ${token}` } });
}
And then wherever you have your <ClerkProvider publishableKey={...}> near the root of your application you can replace it with this instead:
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 api.extend(...) on every api request, because the token does not necessarily stay constant throughout the lifetime of the application. But at least this way you don't have to go through hooks for every request.
The fact that it offers () => Promise<HTTPHeaders> as an option means that for my use case, I actually was able to initialize the api once and then have it call an async callback to get the headers every time (which would then do getToken). I'm not sure what API / HTTP tech you're working with and if that's an option for you.
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
Activity
JonLoesch commentedon Jul 28, 2023
I'm not a Clerk expert but I ran into a similar issue (with react not expo). My workaround was to manually instantiate a
Clerkinstance (had to import@clerk/clerk-jsdirectly) and then have it exported from some util module, globally available. Then it was a simple matter of getting the token from the global object (see here). Judging from my brief look through the source, a singleton object is what the<ClerkProvider>does anyway -- but if you construct it yourself and pass it in via theClerkprop, then you have direct access to the clerk object outside of the react tree.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
window.Clerkreference to the singleton object ... so maybe that's the recommended approach to getting global imperative access? idk, I just am more comfortable with controlling initialization myself so I don't have to worry about order of operations when it comes to initializing clerk and my fetch middleware.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.
nvsd commentedon Jul 30, 2023
@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
getTokenfunction to another function.Doing the above was a poor DX to me, and since I'm using react query, it didn't solve the issue.
I had to do a lot of work to make a semi-decent DX for the rest of the devs on my team but ultimately ended up with
and using that looks like
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
await getTokeninside the function you use to fetch makes API design so awkward.dimkl commentedon Aug 4, 2023
hello @nvsd and thank you for opening this issue.
The
Clerkfromimport {Clerk} from @clerk/expois being populated whenClerkProvideris being loaded.I think that using the above code inside a component (inside ClerkProvider), it will be populated.
I think you could also try the following solutions:
JonLoesch commentedon Aug 5, 2023
@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
api, rather than as a parameter in a hook callback. Assuming I have understood that correctly, then what you could have is something like (This is basically a re-working of existing code on this thread):And then wherever you have your
<ClerkProvider publishableKey={...}>near the root of your application you can replace it with this instead: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
api.extend(...)on every api request, because the token does not necessarily stay constant throughout the lifetime of the application. But at least this way you don't have to go through hooks for every request.JonLoesch commentedon Aug 5, 2023
P.S. This is not clerk related but maybe still worth mentioning?
I said you needed to call
api.extend(...)on every API request. I'm not 100% sure that's true because I don't know what API / HTTP tech you're using.For example, I'm using in a trpc application which has this for custom headers:
The fact that it offers
() => Promise<HTTPHeaders>as an option means that for my use case, I actually was able to initialize the api once and then have it call an async callback to get the headers every time (which would then dogetToken). I'm not sure what API / HTTP tech you're working with and if that's an option for you.phuhl commentedon Aug 23, 2023
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