Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/dry-bobcats-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@clerk/react': major
'@clerk/expo': major
'@clerk/nextjs': major
'@clerk/react-router': major
'@clerk/tanstack-react-start': major
'@clerk/chrome-extension': patch
'@clerk/elements': patch
---

Remove `initialAuthState` option from `useAuth` hook.

This option was mainly used internally but is no longer necessary, so we are removing it to keep the API simple.

If you want `useAuth` to return a populated auth state before Clerk has fully loaded, for example during server rendering, see your framework-specific documentation for guidance.
4 changes: 2 additions & 2 deletions packages/expo/src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { SessionJWTCache } from '../cache';
* This hook extends the useAuth hook to add experimental JWT caching.
* The caching is used only when no options are passed to getToken.
*/
export const useAuth = (initialAuthState?: any): UseAuthReturn => {
const { getToken: getTokenBase, ...rest } = useAuthBase(initialAuthState);
export const useAuth = (options?: Parameters<typeof useAuthBase>[0]): UseAuthReturn => {
const { getToken: getTokenBase, ...rest } = useAuthBase(options);

const getToken: GetToken = (opts?: GetTokenOptions): Promise<string | null> =>
getTokenBase(opts)
Expand Down
17 changes: 11 additions & 6 deletions packages/nextjs/src/app-router/client/ClerkProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';
import { ClerkProvider as ReactClerkProvider } from '@clerk/react';
import { InitialAuthStateProvider as ReactInitialAuthStateProvider } from '@clerk/react/internal';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/navigation';
import React from 'react';
Expand Down Expand Up @@ -37,12 +38,6 @@ const NextClientClerkProvider = (props: NextClerkProviderProps) => {
}
}, []);

// Avoid rendering nested ClerkProviders by checking for the existence of the ClerkNextOptions context provider
const isNested = Boolean(useClerkNextOptions());
if (isNested) {
return props.children;
}

useSafeLayoutEffect(() => {
window.__unstable__onBeforeSetActive = intent => {
/**
Expand Down Expand Up @@ -112,6 +107,16 @@ export const ClientClerkProvider = (props: NextClerkProviderProps & { disableKey
const { children, disableKeyless = false, ...rest } = props;
const safePublishableKey = mergeNextClerkPropsWithEnv(rest).publishableKey;

// Avoid rendering nested ClerkProviders by checking for the existence of the ClerkNextOptions context provider
const isNested = Boolean(useClerkNextOptions());
if (isNested) {
if (rest.initialState) {
// If using <ClerkProvider dynamic> inside a <ClerkProvider>, we do want the initial state to be available for this subtree
return <ReactInitialAuthStateProvider initialState={rest.initialState}>{children}</ReactInitialAuthStateProvider>;
}
return children;
}

if (safePublishableKey || !canUseKeyless || disableKeyless) {
return <NextClientClerkProvider {...rest}>{children}</NextClientClerkProvider>;
}
Expand Down
55 changes: 15 additions & 40 deletions packages/nextjs/src/app-router/server/ClerkProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { InitialState, Without } from '@clerk/shared/types';
import type { Without } from '@clerk/shared/types';
import { headers } from 'next/headers';
import type { ReactNode } from 'react';
import React from 'react';

import { PromisifiedAuthProvider } from '../../client-boundary/PromisifiedAuthProvider';
import { getDynamicAuthData } from '../../server/buildClerkProps';
import type { NextClerkProviderProps } from '../../types';
import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithEnv';
Expand Down Expand Up @@ -32,28 +30,15 @@ export async function ClerkProvider(
) {
const { children, dynamic, ...rest } = props;

async function generateStatePromise() {
if (!dynamic) {
return Promise.resolve(null);
}
return getDynamicClerkState();
}

async function generateNonce() {
if (!dynamic) {
return Promise.resolve('');
}
return getNonceHeaders();
}
const statePromiseOrValue = dynamic ? getDynamicClerkState() : null;
const noncePromiseOrValue = dynamic ? getNonceHeaders() : '';

const propsWithEnvs = mergeNextClerkPropsWithEnv({
...rest,
});

const { shouldRunAsKeyless, runningWithClaimedKeys } = await getKeylessStatus(propsWithEnvs);

let output: ReactNode;

try {
const detectKeylessEnvDrift = await import('../../server/keyless-telemetry.js').then(
mod => mod.detectKeylessEnvDrift,
Expand All @@ -64,35 +49,25 @@ export async function ClerkProvider(
}

if (shouldRunAsKeyless) {
output = (
return (
<KeylessProvider
rest={propsWithEnvs}
generateNonce={generateNonce}
generateStatePromise={generateStatePromise}
nonce={await noncePromiseOrValue}
initialState={await statePromiseOrValue}
runningWithClaimedKeys={runningWithClaimedKeys}
>
{children}
</KeylessProvider>
);
} else {
output = (
<ClientClerkProvider
{...propsWithEnvs}
nonce={await generateNonce()}
initialState={await generateStatePromise()}
>
{children}
</ClientClerkProvider>
);
}

if (dynamic) {
return (
// TODO: fix types so AuthObject is compatible with InitialState
<PromisifiedAuthProvider authPromise={generateStatePromise() as unknown as Promise<InitialState>}>
{output}
</PromisifiedAuthProvider>
);
}
return output;
return (
<ClientClerkProvider
{...propsWithEnvs}
nonce={await noncePromiseOrValue}
initialState={await statePromiseOrValue}
>
{children}
</ClientClerkProvider>
);
}
14 changes: 7 additions & 7 deletions packages/nextjs/src/app-router/server/keyless-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ export async function getKeylessStatus(
type KeylessProviderProps = PropsWithChildren<{
rest: Without<NextClerkProviderProps, '__unstable_invokeMiddlewareOnAuthStateChange'>;
runningWithClaimedKeys: boolean;
generateStatePromise: () => Promise<AuthObject | null>;
generateNonce: () => Promise<string>;
initialState: AuthObject | null;
nonce: string;
}>;

export const KeylessProvider = async (props: KeylessProviderProps) => {
const { rest, runningWithClaimedKeys, generateNonce, generateStatePromise, children } = props;
const { rest, runningWithClaimedKeys, initialState, nonce, children } = props;

// NOTE: Create or read keys on every render. Usually this means only on hard refresh or hard navigations.
const newOrReadKeys = await import('../../server/keyless-node.js')
Expand All @@ -56,8 +56,8 @@ export const KeylessProvider = async (props: KeylessProviderProps) => {
return (
<ClientClerkProvider
{...mergeNextClerkPropsWithEnv(rest)}
nonce={await generateNonce()}
initialState={await generateStatePromise()}
nonce={nonce}
initialState={initialState}
disableKeyless
>
{children}
Expand All @@ -75,8 +75,8 @@ export const KeylessProvider = async (props: KeylessProviderProps) => {
// Explicitly use `null` instead of `undefined` here to avoid persisting `deleteKeylessAction` during merging of options.
__internal_keyless_dismissPrompt: runningWithClaimedKeys ? deleteKeylessAction : null,
})}
nonce={await generateNonce()}
initialState={await generateStatePromise()}
nonce={nonce}
initialState={initialState}
>
{children}
</ClientClerkProvider>
Expand Down
78 changes: 0 additions & 78 deletions packages/nextjs/src/client-boundary/PromisifiedAuthProvider.tsx

This file was deleted.

3 changes: 1 addition & 2 deletions packages/nextjs/src/client-boundary/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

export {
useAuth,
useClerk,
useEmailLink,
useOrganization,
Expand All @@ -23,5 +24,3 @@ export {
EmailLinkErrorCode,
EmailLinkErrorCodeStatus,
} from '@clerk/react/errors';

export { usePromisifiedAuth as useAuth } from './PromisifiedAuthProvider';
23 changes: 0 additions & 23 deletions packages/react/src/contexts/AuthContext.ts

This file was deleted.

84 changes: 84 additions & 0 deletions packages/react/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { DeriveStateReturnType } from '@clerk/shared/deriveState';
import { deriveFromClientSideState, deriveFromSsrInitialState } from '@clerk/shared/deriveState';
import { createContextAndHook } from '@clerk/shared/react';
import type {
ActClaim,
InitialState,
JwtPayload,
OrganizationCustomPermissionKey,
OrganizationCustomRoleKey,
Resources,
SessionStatusClaim,
} from '@clerk/shared/types';
import React from 'react';

export type AuthContextValue = {
userId: string | null | undefined;
sessionId: string | null | undefined;
sessionStatus: SessionStatusClaim | null | undefined;
sessionClaims: JwtPayload | null | undefined;
actor: ActClaim | null | undefined;
orgId: string | null | undefined;
orgRole: OrganizationCustomRoleKey | null | undefined;
orgSlug: string | null | undefined;
orgPermissions: OrganizationCustomPermissionKey[] | null | undefined;
factorVerificationAge: [number, number] | null;
};

export const [InitialAuthContext, useInitialAuthContext] = createContextAndHook<AuthContextValue | undefined>(
'InitialAuthContext',
);
export function InitialAuthStateProvider(props: { children: React.ReactNode; initialState: InitialState | undefined }) {
const initialAuthStateCtxValue = useDeriveAuthContext(
props.initialState ? deriveFromSsrInitialState(props.initialState) : undefined,
);
return <InitialAuthContext.Provider value={initialAuthStateCtxValue}>{props.children}</InitialAuthContext.Provider>;
}

export const [AuthContext, useAuthContext] = createContextAndHook<AuthContextValue>('AuthContext');
export function AuthStateProvider(props: { children: React.ReactNode; state: Resources }) {
const authStateCtxValue = useDeriveAuthContext(deriveFromClientSideState(props.state));
return <AuthContext.Provider value={authStateCtxValue}>{props.children}</AuthContext.Provider>;
}

const emptyAuthCtx = { value: undefined };
// We want the types to be:
// Pass in value known not to be undefined -> { value: AuthContextValue }
// Pass in value that might be undefined -> { value: AuthContextValue | undefined }
type DerivedAuthContextValue<T> = { value: T extends undefined ? undefined : AuthContextValue };

// Narrow full state to only what we need for the AuthContextValue
function useDeriveAuthContext<T extends DeriveStateReturnType | undefined>(fullState: T): DerivedAuthContextValue<T> {
const fullReturn = React.useMemo(() => {
const value = {
sessionId: fullState?.sessionId,
sessionStatus: fullState?.sessionStatus,
sessionClaims: fullState?.sessionClaims,
userId: fullState?.userId,
actor: fullState?.actor,
orgId: fullState?.orgId,
orgRole: fullState?.orgRole,
orgSlug: fullState?.orgSlug,
orgPermissions: fullState?.orgPermissions,
factorVerificationAge: fullState?.factorVerificationAge,
};
return { value };
}, [
fullState?.sessionId,
fullState?.sessionStatus,
fullState?.sessionClaims,
fullState?.userId,
fullState?.actor,
fullState?.orgId,
fullState?.orgRole,
fullState?.orgSlug,
fullState?.orgPermissions,
fullState?.factorVerificationAge,
]);

if (fullState === undefined) {
return emptyAuthCtx as DerivedAuthContextValue<T>;
}

return fullReturn as DerivedAuthContextValue<T>;
}
Loading
Loading