-
Notifications
You must be signed in to change notification settings - Fork 8
Description
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
- Configure
AuthKitProviderwithonRefreshcallback - User authenticates and selects an organization during WorkOS login flow
- Check
useAuth().organizationIdafter authentication completes
Expected Behavior
useAuth().organizationId should return the organization ID (e.g., "org_01KEWJVGA8A9WS4QT6F22MBCS8")
Actual Behavior
useAuth().organizationId returns null, even though:
- The WorkOS API returns
organization_idin the authentication response - The
onRefreshcallback receives the correctorganizationId - The JWT contains the correct
org_idclaim
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
organizationIdto determine if a user has selected an organization - Uses
organizationIdfor authorization decisions - Redirects based on organization membership
Without the workaround, this causes infinite redirect loops when checking if (!organizationId) { signIn() }.