diff --git a/.changeset/nextjs-signed-out-defaults.md b/.changeset/nextjs-signed-out-defaults.md new file mode 100644 index 000000000..3830504b3 --- /dev/null +++ b/.changeset/nextjs-signed-out-defaults.md @@ -0,0 +1,5 @@ +--- +'@asgardeo/nextjs': patch +--- + +`useAsgardeo().user` and the current organization are `null` instead of empty objects while signed out or unavailable, so `` and `` render their fallbacks and `OrganizationSwitcher` no longer offers to manage an organization with an empty ID. With `preferences.user.fetchUserProfile` set to `false`, the user and profile are now populated from the ID token claims, as in the React SDK, instead of being left empty. diff --git a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx index de77065f8..ace6a8f0c 100644 --- a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx @@ -66,7 +66,7 @@ export type AsgardeoClientProviderProps = Partial Promise; createOrganization: (payload: CreateOrganizationPayload, sessionId: string) => Promise; - currentOrganization: Organization; + currentOrganization: Organization | null; getAllOrganizations: (options?: any, sessionId?: string) => Promise; handleOAuthCallback: ( code: string, diff --git a/packages/nextjs/src/server/AsgardeoProvider.tsx b/packages/nextjs/src/server/AsgardeoProvider.tsx index 5015697ac..67e33bbd8 100644 --- a/packages/nextjs/src/server/AsgardeoProvider.tsx +++ b/packages/nextjs/src/server/AsgardeoProvider.tsx @@ -18,7 +18,15 @@ 'use server'; -import {BrandingPreference, AsgardeoRuntimeError, IdToken, Organization, User, UserProfile} from '@asgardeo/node'; +import { + BrandingPreference, + AsgardeoRuntimeError, + IdToken, + Organization, + User, + UserProfile, + extractUserClaimsFromIdToken, +} from '@asgardeo/node'; import {AsgardeoProviderProps} from '@asgardeo/react'; import {FC, PropsWithChildren, ReactElement} from 'react'; import clearSession from './actions/clearSession'; @@ -116,17 +124,15 @@ const AsgardeoServerProvider: FC> const sessionId: string = sessionPayload?.sessionId || (await getSessionId()) || ''; const signedIn: boolean = await isSignedIn(sessionId); - let user: User = {}; + // `null` (not an empty object) while signed out or unavailable, so that `` / `` render + // their fallbacks and `useAsgardeo().user` can be checked for truthiness, as in the React SDK. + let user: User | null = null; let userProfile: UserProfile = { flattenedProfile: {}, profile: {}, schemas: [], }; - let currentOrganization: Organization = { - id: '', - name: '', - orgHandle: '', - }; + let currentOrganization: Organization | null = null; let myOrganizations: Organization[] = []; let brandingPreference: BrandingPreference | null = null; @@ -166,11 +172,22 @@ const AsgardeoServerProvider: FC> success: boolean; } = await getUserProfileAction(sessionId); - user = userResponse.data?.user || {}; + user = userResponse.data?.user ?? null; userProfile = userProfileResponse.data?.userProfile ?? userProfile; } catch (error) { logger.warn('[AsgardeoServerProvider] Failed to fetch user profile from SCIM2:', error?.toString()); } + } else { + // Profile fetching is disabled: expose the claims of the ID token instead, as the React SDK does, so + // that `user` and `` are not empty. + try { + const claims: User = extractUserClaimsFromIdToken(await asgardeoClient.getDecodedIdToken(sessionId)) as User; + + user = claims; + userProfile = {flattenedProfile: claims, profile: claims, schemas: []}; + } catch (error) { + logger.warn('[AsgardeoServerProvider] Failed to read the user claims from the ID token:', error?.toString()); + } } if (shouldFetchOrganizations) { @@ -187,7 +204,7 @@ const AsgardeoServerProvider: FC> logger.warn('[AsgardeoServerProvider] No session ID available, skipping organization fetch'); } - currentOrganization = currentOrganizationResponse?.data?.organization as Organization; + currentOrganization = currentOrganizationResponse?.data?.organization ?? null; } catch (error) { logger.warn('[AsgardeoServerProvider] Failed to fetch organization info:', error?.toString()); }