Skip to content

Commit ca17912

Browse files
thomasballingerConvex, Inc.
authored andcommitted
Make Clerk auth provider reactive on orgId and orgRole (#31752)
When the client-side Clerk SDK changes the orgId it now clears the Convex auth and submits a new auth token. GitOrigin-RevId: a71436030fd5ba7e8a7de973c82ab6e6211c8294
1 parent 26d9289 commit ca17912

File tree

2 files changed

+46
-15
lines changed

2 files changed

+46
-15
lines changed

src/react-clerk/ConvexProviderWithClerk.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ type UseAuth = () => {
1919
template?: "convex";
2020
skipCache?: boolean;
2121
}) => Promise<string | null>;
22+
// We don't use the properties but they should trigger a new token fetch.
23+
orgId: string | undefined | null;
24+
orgRole: string | undefined | null;
2225
};
2326

2427
/**
@@ -42,7 +45,7 @@ export function ConvexProviderWithClerk({
4245
}: {
4346
children: ReactNode;
4447
client: IConvexReactClient;
45-
useAuth: UseAuth;
48+
useAuth: UseAuth; // useAuth from Clerk
4649
}) {
4750
const useAuthFromClerk = useUseAuthFromClerk(useAuth);
4851
return (
@@ -56,7 +59,7 @@ function useUseAuthFromClerk(useAuth: UseAuth) {
5659
return useMemo(
5760
() =>
5861
function useAuthFromClerk() {
59-
const { isLoaded, isSignedIn, getToken } = useAuth();
62+
const { isLoaded, isSignedIn, getToken, orgId, orgRole } = useAuth();
6063
const fetchAccessToken = useCallback(
6164
async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
6265
try {
@@ -68,9 +71,10 @@ function useUseAuthFromClerk(useAuth: UseAuth) {
6871
return null;
6972
}
7073
},
71-
// Clerk is not memoizing its getToken function at all
74+
// Build a new fetchAccessToken to trigger setAuth() whenever these change.
75+
// Anything else from the JWT Clerk wants to be reactive goes here too.
7276
// eslint-disable-next-line react-hooks/exhaustive-deps
73-
[],
77+
[getToken, orgId, orgRole],
7478
);
7579
return useMemo(
7680
() => ({

src/react/ConvexAuthState.tsx

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ export function useConvexAuth(): {
6565
* should be a React hook that returns the provider's authentication state
6666
* and a function to fetch a JWT access token.
6767
*
68+
* If the `useAuth` prop function updates causing a rerender then auth state
69+
* wil transition to loading and the `fetchAccessToken()` function called again.
70+
*
6871
* See [Custom Auth Integration](https://docs.convex.dev/auth/advanced/custom-auth) for more information.
6972
*
7073
* @public
@@ -84,20 +87,25 @@ export function ConvexProviderWithAuth({
8487
}) => Promise<string | null>;
8588
};
8689
}) {
87-
const { isLoading, isAuthenticated, fetchAccessToken } = useAuth();
90+
const {
91+
isLoading: tokenLoading,
92+
isAuthenticated,
93+
fetchAccessToken,
94+
} = useAuth();
8895
const [isConvexAuthenticated, setIsConvexAuthenticated] = useState<
8996
boolean | null
9097
>(null);
9198

92-
// If the useAuth went back to the loading state (which is unusual but possible)
99+
// If the useAuth went back to the tokenLoading state (which is unusual but possible)
93100
// reset the Convex auth state to null so that we can correctly
94101
// transition the state from "loading" to "authenticated"
95102
// without going through "unauthenticated".
96-
if (isLoading && isConvexAuthenticated !== null) {
103+
if (tokenLoading && isConvexAuthenticated !== null) {
97104
setIsConvexAuthenticated(null);
98105
}
99106

100-
if (!isLoading && !isAuthenticated && isConvexAuthenticated !== false) {
107+
// If the useAuth goes to not authenticated then isConvexAuthenticated should reflect that.
108+
if (!tokenLoading && !isAuthenticated && isConvexAuthenticated !== false) {
101109
setIsConvexAuthenticated(false);
102110
}
103111

@@ -111,16 +119,17 @@ export function ConvexProviderWithAuth({
111119
<ConvexAuthStateFirstEffect
112120
isAuthenticated={isAuthenticated}
113121
fetchAccessToken={fetchAccessToken}
114-
isLoading={isLoading}
122+
isLoading={tokenLoading}
115123
client={client}
116124
setIsConvexAuthenticated={setIsConvexAuthenticated}
117125
/>
118126
<ConvexProvider client={client as any}>{children}</ConvexProvider>
119127
<ConvexAuthStateLastEffect
120128
isAuthenticated={isAuthenticated}
121129
fetchAccessToken={fetchAccessToken}
122-
isLoading={isLoading}
130+
isLoading={tokenLoading}
123131
client={client}
132+
setIsConvexAuthenticated={setIsConvexAuthenticated}
124133
/>
125134
</ConvexAuthContext.Provider>
126135
);
@@ -148,16 +157,16 @@ function ConvexAuthStateFirstEffect({
148157
useEffect(() => {
149158
let isThisEffectRelevant = true;
150159
if (isAuthenticated) {
151-
client.setAuth(fetchAccessToken, (isAuthenticated) => {
160+
client.setAuth(fetchAccessToken, (backendReportsIsAuthenticated) => {
152161
if (isThisEffectRelevant) {
153-
setIsConvexAuthenticated(isAuthenticated);
162+
setIsConvexAuthenticated(() => backendReportsIsAuthenticated);
154163
}
155164
});
156165
return () => {
157166
isThisEffectRelevant = false;
158167

159-
// If we haven't finished fetching the token by now
160-
// we shouldn't transition to a loaded state
168+
// If unmounting or something changed before we finished fetching the token
169+
// we shouldn't transition to a loaded state.
161170
setIsConvexAuthenticated((isConvexAuthenticated) =>
162171
isConvexAuthenticated ? false : null,
163172
);
@@ -181,20 +190,38 @@ function ConvexAuthStateLastEffect({
181190
fetchAccessToken,
182191
isLoading,
183192
client,
193+
setIsConvexAuthenticated,
184194
}: {
185195
isAuthenticated: boolean;
186196
fetchAccessToken: (args: {
187197
forceRefreshToken: boolean;
188198
}) => Promise<string | null>;
189199
isLoading: boolean;
190200
client: IConvexReactClient;
201+
setIsConvexAuthenticated: React.Dispatch<
202+
React.SetStateAction<boolean | null>
203+
>;
191204
}) {
192205
useEffect(() => {
206+
// If rendered with isAuthenticated=true then clear that auth on in cleanup.
193207
if (isAuthenticated) {
194208
return () => {
195209
client.clearAuth();
210+
// Set state back to loading in case this is a transition from one
211+
// fetchToken function to another which signals a new auth context,
212+
// e.g. a new orgId from Clerk. Auth context changes like this
213+
// return isAuthenticated: true from useAuth() but if
214+
// useQuth reports isAuthenticated: false on the next render
215+
// then this null value will be overridden to false.
216+
setIsConvexAuthenticated(() => null);
196217
};
197218
}
198-
}, [isAuthenticated, fetchAccessToken, isLoading, client]);
219+
}, [
220+
isAuthenticated,
221+
fetchAccessToken,
222+
isLoading,
223+
client,
224+
setIsConvexAuthenticated,
225+
]);
199226
return null;
200227
}

0 commit comments

Comments
 (0)