Authentication States
Jazz provides three distinct authentication states that determine how users interact with your app: Anonymous Authentication, Guest Mode, and Authenticated Account.
Anonymous Authentication
When a user loads a Jazz application for the first time, we create a new Account by generating keys and storing them locally:
- Users have full accounts with unique IDs
- Data persists between sessions on the same device
- Can be upgraded to a full account (passkey, passphrase, etc.)
- Data syncs across the network (if enabled)
Authenticated Account
Authenticated Account provides full multi-device functionality:
- Persistent identity across multiple devices
- Full access to all application features
- Data can sync across all user devices
- Multiple authentication methods available
Guest Mode
Guest Mode provides a completely accountless context:
- No persistent identity or account
- Only provides access to publicly readable content
- Cannot save or sync user-specific data
- Suitable for read-only access to public resources
Detecting Authentication State
You can detect the current authentication state using useAgent and useIsAuthenticated.
import {function useAgent<A extends AccountClass<Account> | CoreAccountSchema = typeof Account>(): AnonymousJazzAgent | Loaded<A, true>React hook for accessing the current agent. An agent can either be: - an Authenticated Account, if the user is logged in - an Anonymous Account, if the user didn't log in - or an anonymous agent, if in guest mode The agent can be used as the `loadAs` parameter for load and subscribe methods.useAgent,function useIsAuthenticated(): booleanuseIsAuthenticated } from "jazz-tools/react"; functionfunction AuthStateIndicator(): React.JSX.ElementAuthStateIndicator() { constconst agent: Account | AnonymousJazzAgentagent =useAgent<typeof Account>(): Account | AnonymousJazzAgentReact hook for accessing the current agent. An agent can either be: - an Authenticated Account, if the user is logged in - an Anonymous Account, if the user didn't log in - or an anonymous agent, if in guest mode The agent can be used as the `loadAs` parameter for load and subscribe methods.useAgent(); constconst isAuthenticated: booleanisAuthenticated =function useIsAuthenticated(): booleanuseIsAuthenticated(); // Check if guest mode is enabled in JazzReactProvider constconst isGuest: booleanisGuest =const agent: Account | AnonymousJazzAgentagent.$type$: "Account" | "Anonymous"$type$ !== "Account" // Anonymous authentication: has an account but not fully authenticated constconst isAnonymous: booleanisAnonymous =const agent: Account | AnonymousJazzAgentagent.$type$: "Account" | "Anonymous"$type$ === "Account" && !const isAuthenticated: booleanisAuthenticated; return ( <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> {const isGuest: booleanisGuest && <React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>Guest Mode</React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>} {const isAnonymous: booleanisAnonymous && <React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>Anonymous Account</React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>} {const isAuthenticated: booleanisAuthenticated && <React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>Authenticated</React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>} </React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> ); }
Migrating data from anonymous to authenticated account
When a user signs up, their anonymous account is transparently upgraded to an authenticated account, preserving all their data.
However, if a user has been using your app anonymously and later logs in with an existing account, their anonymous account data would normally be discarded. To prevent data loss, you can use the onAnonymousAccountDiscarded handler.
This example from our music player example app shows how to migrate data:
export async functionfunction onAnonymousAccountDiscarded(anonymousAccount: MusicaAccount): Promise<void>onAnonymousAccountDiscarded(anonymousAccount:anonymousAccount: { readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & AccountMusicaAccount, ) { const {type MusicaAccount = { readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Accountroot:Account.root: { readonly rootPlaylist: CoMapLikeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap, { ...; }, 10, [...]>; } & { ...; } & CoMapanonymousAccountRoot } = awaitconst anonymousAccountRoot: { readonly rootPlaylist: CoMapLikeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap, { ...; }, 10, [...]>; } & { ...; } & CoMapanonymousAccount.anonymousAccount: { readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & AccountAccount.$jazz: AccountJazzApi<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account>Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.$jazz.ensureLoaded({AccountJazzApi<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account>.ensureLoaded<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account, { ...; }>(this: AccountJazzApi<...>, options: { ...; }): Promise<...>resolve: {resolve: RefsToResolve<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account, 10, []>root: {root?: RefsToResolve<{ ...; } & CoMap, 10, [...]> | undefinedrootPlaylist: {rootPlaylist?: RefsToResolve<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap, 10, [...]> | undefinedtracks: {tracks?: RefsToResolve<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>, 10, [0, 0, 0]> | undefined$each: true, }, }, }, }, }); const$each?: RefsToResolve<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap, 10, [0, 0, 0, 0]> | undefinedme = awaitconst me: CoMapLikeLoaded<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account, { ...; }, 10, []>MusicaAccount.const MusicaAccount: co.Account<{ root: co.Map<{ rootPlaylist: co.Map<{ title: z.z.ZodString; tracks: co.List<co.Map<{ title: z.z.ZodString; duration: z.z.ZodNumber; isExampleTrack: z.ZodOptional<z.z.ZodBoolean>; }, unknown, Account | Group, true>, true>; }, unknown, Account | Group, true>; }, unknown, Account | Group, true>; profile: co.Profile<...>; }, true>getMe().AccountSchema<{ root: CoMapSchema<{ rootPlaylist: CoMapSchema<{ title: ZodString; tracks: CoListSchema<CoMapSchema<{ title: ZodString; duration: ZodNumber; isExampleTrack: ZodOptional<ZodBoolean>; }, unknown, Account | Group, true>, true>; }, unknown, Account | Group, true>; }, unknown, Account | Group, true>; profile: CoProfileSchema<...>; }, true>.getMe(): { readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & AccountAccount.$jazz: AccountJazzApi<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account>Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.$jazz.ensureLoaded({AccountJazzApi<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account>.ensureLoaded<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account, { ...; }>(this: AccountJazzApi<...>, options: { ...; }): Promise<...>resolve: {resolve: RefsToResolve<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account, 10, []>root: {root?: RefsToResolve<{ ...; } & CoMap, 10, [...]> | undefinedrootPlaylist: {rootPlaylist?: RefsToResolve<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap, 10, [...]> | undefinedtracks: true, }, }, }, }); // @ts-expect-error TODO: fix issue that prevents iterating CoLists inside loaded CoMaps for (consttracks?: RefsToResolve<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>, 10, [0, 0, 0]> | undefinedconst track: anytrack ofanonymousAccountRoot.const anonymousAccountRoot: { readonly rootPlaylist: CoMapLikeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap, { ...; }, 10, [...]>; } & { ...; } & CoMaprootPlaylist.rootPlaylist: { readonly tracks: readonly ({ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap)[] & CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>; } & { readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMaptracks) { if (tracks: readonly ({ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap)[] & CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>const track: anytrack.isExampleTrack) continue; constconst trackGroup: anytrackGroup =const track: anytrack.$jazz.owner;const trackGroup: anytrackGroup.addMember(me, "admin");const me: CoMapLikeLoaded<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account, { ...; }, 10, []>me.const me: CoMapLikeLoaded<{ readonly root: MaybeLoaded<{ readonly rootPlaylist: MaybeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap>; } & CoMap>; readonly profile: MaybeLoaded<...>; } & Account, { ...; }, 10, []>root.Account.root: { readonly rootPlaylist: CoMapLikeLoaded<{ readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMap, { ...; }, 10, [...]>; } & { ...; } & CoMaprootPlaylist.rootPlaylist: { readonly tracks: CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>; } & { readonly title: string; readonly tracks: MaybeLoaded<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>; } & CoMaptracks.tracks: CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>$jazz.CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>.$jazz: CoListJazzApi<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>CoListJazzApi<CoList<MaybeLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap>>>.push(...items: (({ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap) | NotLoaded<{ readonly title: string; readonly duration: number; readonly isExampleTrack: boolean | undefined; } & CoMap> | CoMapInit<...>)[]): numberAppends new elements to the end of an array, and returns the new length of the array.push(const track: anytrack); } }
To see how this works, try uploading a song in the music player demo and then log in with an existing account.
Provider Configuration for Authentication
You can configure how authentication states work in your app with the JazzReactProvider. The provider offers several options that impact authentication behavior:
guestMode: Enable/disable Guest ModeonAnonymousAccountDiscarded: Handle data migration when switching accountssync.when: Control when data synchronization happensdefaultProfileName: Set default name for new user profiles
For detailed information on all provider options, see Provider Configuration options.
Controlling sync for different authentication states
You can control network sync with Providers based on authentication state:
when: "always": Sync is enabled for both Anonymous Authentication and Authenticated Accountwhen: "signedUp": Sync is enabled when the user is authenticatedwhen: "never": Sync is disabled, content stays local
<function JazzReactProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, enableSSR, fallback, authSecretStorageKey, }: JazzProviderProps<S>): JSX.ElementJazzReactProvidersync: SyncConfigsync={{peer: `wss://${string}` | `ws://${string}`peer: `wss://cloud.jazz.tools/?key=${const apiKey: "you@example.com"Use your email as a temporary key, or get a free API Key at dashboard.jazz.tools for higher limits.apiKey}`, // Controls when sync is enabled for // both Anonymous Authentication and Authenticated Accountwhen?: "always" | "signedUp" | undefinedwhen: "always", // or "signedUp" or "never" }} > <function App(): React.JSX.ElementApp /> </function JazzReactProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, enableSSR, fallback, authSecretStorageKey, }: JazzProviderProps<S>): JSX.ElementJazzReactProvider>
Disable sync for Anonymous Authentication
You can disable network sync to make your app local-only under specific circumstances.
For example, you may want to give users with Anonymous Authentication the opportunity to try your app locally-only (incurring no sync traffic), then enable network sync only when the user is fully authenticated.
<function JazzReactProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, enableSSR, fallback, authSecretStorageKey, }: JazzProviderProps<S>): JSX.ElementJazzReactProvidersync: SyncConfigsync={{peer: `wss://${string}` | `ws://${string}`peer: `wss://cloud.jazz.tools/?key=${const apiKey: "you@example.com"Use your email as a temporary key, or get a free API Key at dashboard.jazz.tools for higher limits.apiKey}`, // This makes the app work in local mode when using Anonymous Authenticationwhen?: "always" | "signedUp" | undefinedwhen: "signedUp", }} > <function App(): React.JSX.ElementApp /> </function JazzReactProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, enableSSR, fallback, authSecretStorageKey, }: JazzProviderProps<S>): JSX.ElementJazzReactProvider>
Configuring Guest Mode Access
You can configure Guest Mode access with the guestMode prop for Providers.
<function JazzReactProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, enableSSR, fallback, authSecretStorageKey, }: JazzProviderProps<S>): JSX.ElementJazzReactProvider // Enable Guest Mode for public contentguestMode?: boolean | undefinedguestMode={true}sync: SyncConfigsync={{peer: `wss://${string}` | `ws://${string}`peer: `wss://cloud.jazz.tools/?key=${const apiKey: "you@example.com"Use your email as a temporary key, or get a free API Key at dashboard.jazz.tools for higher limits.apiKey}`, // Only sync for authenticated userswhen?: "always" | "signedUp" | undefinedwhen: "signedUp", }} > <function App(): React.JSX.ElementApp /> </function JazzReactProvider<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>({ children, guestMode, sync, storage, AccountSchema, defaultProfileName, onLogOut, logOutReplacement, onAnonymousAccountDiscarded, enableSSR, fallback, authSecretStorageKey, }: JazzProviderProps<S>): JSX.ElementJazzReactProvider>
For more complex behaviours, you can manually control sync by statefully switching when between "always" and "never".