Skip to content

useAuth() returns organizationId: null after onRefresh callback receives valid organizationId #87

@simon-hng

Description

@simon-hng

Bug Description

useAuth().organizationId is always null even when the onRefresh callback receives a valid organizationId. This causes issues when checking if a user has an organization after authentication.

Environment

  • @workos-inc/authkit-react: 0.16.0
  • @workos-inc/authkit-js: 0.17.0
  • React: 18.x
  • Browser: Chrome (also reproducible in other browsers)

Steps to Reproduce

  1. Configure AuthKitProvider with onRefresh callback
  2. User authenticates and selects an organization during WorkOS login flow
  3. Check useAuth().organizationId after authentication completes

Expected Behavior

useAuth().organizationId should return the organization ID (e.g., "org_01KEWJVGA8A9WS4QT6F22MBCS8")

Actual Behavior

useAuth().organizationId returns null, even though:

  1. The WorkOS API returns organization_id in the authentication response
  2. The onRefresh callback receives the correct organizationId
  3. The JWT contains the correct org_id claim

Console Evidence

Adding logging to verify:

<AuthKitProvider
  onRefresh={(response) => {
    console.log('onRefresh organizationId:', response.organizationId);
  }}
>

Output:

onRefresh organizationId: org_01KEWJVGA8A9WS4QT6F22MBCS8
// But useAuth() returns { organizationId: null } on every subsequent render

Root Cause Analysis

The bug is in AuthKitProvider in src/provider.tsx. Here's the problematic flow:

Step What Happens organizationId State
1 createClient() is called null (initial)
2 Session refresh triggers onRefresh callback null
3 handleRefresh calls setState() with correct organizationId "org_01..."
4 createClient() promise resolves "org_01..."
5 .then() block calls setState(readyState) null

The Bug (lines ~196-197 in compiled code)

// In the .then() block after createClient resolves:
const readyState = { ...initialState, isLoading: false, user };
setState(readyState);  // BUG: This overwrites organizationId set by handleRefresh!

The problem is that readyState spreads from initialState, which has organizationId: null. This overwrites the organizationId that was correctly set by handleRefresh moments earlier.

Proposed Fix

Change the .then() block to preserve existing state instead of resetting to initialState:

- const readyState = { ...initialState, isLoading: false, user };
- setState(readyState);
+ setState((prev) => ({ ...prev, isLoading: false, user }));

Workaround

Until this is fixed, we're using a workaround that captures organizationId from onRefresh before it gets overwritten:

// Create a context to store the real organizationId
const OrgIdContext = createContext<string | null>(null);

function AuthKitWrapper({ children }) {
  const [orgId, setOrgId] = useState<string | null>(null);
  
  return (
    <OrgIdContext.Provider value={orgId}>
      <AuthKitProvider
        onRefresh={(response) => {
          // Capture before authkit-react resets it
          if (response.organizationId) {
            setOrgId(response.organizationId);
          }
        }}
      >
        {children}
      </AuthKitProvider>
    </OrgIdContext.Provider>
  );
}

// Custom hook that uses the workaround
function useAuth() {
  const authKitState = useAuthKit();
  const orgIdFromRefresh = useContext(OrgIdContext);
  
  return {
    ...authKitState,
    organizationId: orgIdFromRefresh ?? authKitState.organizationId,
  };
}

Impact

This bug breaks any application that:

  • Checks organizationId to determine if a user has selected an organization
  • Uses organizationId for authorization decisions
  • Redirects based on organization membership

Without the workaround, this causes infinite redirect loops when checking if (!organizationId) { signIn() }.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions