Skip to content

Commit

Permalink
Make Clerk auth provider reactive on orgId and orgRole (#31752)
Browse files Browse the repository at this point in the history
When the client-side Clerk SDK changes the orgId it now clears the Convex auth and submits a new auth token.

GitOrigin-RevId: a71436030fd5ba7e8a7de973c82ab6e6211c8294
  • Loading branch information
thomasballinger authored and Convex, Inc. committed Nov 26, 2024
1 parent 26d9289 commit ca17912
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 15 deletions.
12 changes: 8 additions & 4 deletions src/react-clerk/ConvexProviderWithClerk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ type UseAuth = () => {
template?: "convex";
skipCache?: boolean;
}) => Promise<string | null>;
// We don't use the properties but they should trigger a new token fetch.
orgId: string | undefined | null;
orgRole: string | undefined | null;
};

/**
Expand All @@ -42,7 +45,7 @@ export function ConvexProviderWithClerk({
}: {
children: ReactNode;
client: IConvexReactClient;
useAuth: UseAuth;
useAuth: UseAuth; // useAuth from Clerk
}) {
const useAuthFromClerk = useUseAuthFromClerk(useAuth);
return (
Expand All @@ -56,7 +59,7 @@ function useUseAuthFromClerk(useAuth: UseAuth) {
return useMemo(
() =>
function useAuthFromClerk() {
const { isLoaded, isSignedIn, getToken } = useAuth();
const { isLoaded, isSignedIn, getToken, orgId, orgRole } = useAuth();
const fetchAccessToken = useCallback(
async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
try {
Expand All @@ -68,9 +71,10 @@ function useUseAuthFromClerk(useAuth: UseAuth) {
return null;
}
},
// Clerk is not memoizing its getToken function at all
// Build a new fetchAccessToken to trigger setAuth() whenever these change.
// Anything else from the JWT Clerk wants to be reactive goes here too.
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
[getToken, orgId, orgRole],
);
return useMemo(
() => ({
Expand Down
49 changes: 38 additions & 11 deletions src/react/ConvexAuthState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export function useConvexAuth(): {
* should be a React hook that returns the provider's authentication state
* and a function to fetch a JWT access token.
*
* If the `useAuth` prop function updates causing a rerender then auth state
* wil transition to loading and the `fetchAccessToken()` function called again.
*
* See [Custom Auth Integration](https://docs.convex.dev/auth/advanced/custom-auth) for more information.
*
* @public
Expand All @@ -84,20 +87,25 @@ export function ConvexProviderWithAuth({
}) => Promise<string | null>;
};
}) {
const { isLoading, isAuthenticated, fetchAccessToken } = useAuth();
const {
isLoading: tokenLoading,
isAuthenticated,
fetchAccessToken,
} = useAuth();
const [isConvexAuthenticated, setIsConvexAuthenticated] = useState<
boolean | null
>(null);

// If the useAuth went back to the loading state (which is unusual but possible)
// If the useAuth went back to the tokenLoading state (which is unusual but possible)
// reset the Convex auth state to null so that we can correctly
// transition the state from "loading" to "authenticated"
// without going through "unauthenticated".
if (isLoading && isConvexAuthenticated !== null) {
if (tokenLoading && isConvexAuthenticated !== null) {
setIsConvexAuthenticated(null);
}

if (!isLoading && !isAuthenticated && isConvexAuthenticated !== false) {
// If the useAuth goes to not authenticated then isConvexAuthenticated should reflect that.
if (!tokenLoading && !isAuthenticated && isConvexAuthenticated !== false) {
setIsConvexAuthenticated(false);
}

Expand All @@ -111,16 +119,17 @@ export function ConvexProviderWithAuth({
<ConvexAuthStateFirstEffect
isAuthenticated={isAuthenticated}
fetchAccessToken={fetchAccessToken}
isLoading={isLoading}
isLoading={tokenLoading}
client={client}
setIsConvexAuthenticated={setIsConvexAuthenticated}
/>
<ConvexProvider client={client as any}>{children}</ConvexProvider>
<ConvexAuthStateLastEffect
isAuthenticated={isAuthenticated}
fetchAccessToken={fetchAccessToken}
isLoading={isLoading}
isLoading={tokenLoading}
client={client}
setIsConvexAuthenticated={setIsConvexAuthenticated}
/>
</ConvexAuthContext.Provider>
);
Expand Down Expand Up @@ -148,16 +157,16 @@ function ConvexAuthStateFirstEffect({
useEffect(() => {
let isThisEffectRelevant = true;
if (isAuthenticated) {
client.setAuth(fetchAccessToken, (isAuthenticated) => {
client.setAuth(fetchAccessToken, (backendReportsIsAuthenticated) => {
if (isThisEffectRelevant) {
setIsConvexAuthenticated(isAuthenticated);
setIsConvexAuthenticated(() => backendReportsIsAuthenticated);
}
});
return () => {
isThisEffectRelevant = false;

// If we haven't finished fetching the token by now
// we shouldn't transition to a loaded state
// If unmounting or something changed before we finished fetching the token
// we shouldn't transition to a loaded state.
setIsConvexAuthenticated((isConvexAuthenticated) =>
isConvexAuthenticated ? false : null,
);
Expand All @@ -181,20 +190,38 @@ function ConvexAuthStateLastEffect({
fetchAccessToken,
isLoading,
client,
setIsConvexAuthenticated,
}: {
isAuthenticated: boolean;
fetchAccessToken: (args: {
forceRefreshToken: boolean;
}) => Promise<string | null>;
isLoading: boolean;
client: IConvexReactClient;
setIsConvexAuthenticated: React.Dispatch<
React.SetStateAction<boolean | null>
>;
}) {
useEffect(() => {
// If rendered with isAuthenticated=true then clear that auth on in cleanup.
if (isAuthenticated) {
return () => {
client.clearAuth();
// Set state back to loading in case this is a transition from one
// fetchToken function to another which signals a new auth context,
// e.g. a new orgId from Clerk. Auth context changes like this
// return isAuthenticated: true from useAuth() but if
// useQuth reports isAuthenticated: false on the next render
// then this null value will be overridden to false.
setIsConvexAuthenticated(() => null);
};
}
}, [isAuthenticated, fetchAccessToken, isLoading, client]);
}, [
isAuthenticated,
fetchAccessToken,
isLoading,
client,
setIsConvexAuthenticated,
]);
return null;
}

0 comments on commit ca17912

Please sign in to comment.