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 useAccountOrGuest and useIsAuthenticated.

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 function function onAnonymousAccountDiscarded(anonymousAccount: MusicaAccount): Promise<void>onAnonymousAccountDiscarded(
  anonymousAccount: MusicaAccountanonymousAccount: class MusicaAccountMusicaAccount,
) {
  const { 
MusicaAccount.root: {
    rootPlaylist: {
        tracks: MusicTrack[] & ListOfTracks;
    } & Playlist;
} & MusicaAccountRoot & co<MusicaAccountRoot | null>
root
:
const anonymousAccountRoot: {
    rootPlaylist: {
        tracks: MusicTrack[] & ListOfTracks;
    } & Playlist;
} & MusicaAccountRoot & co<MusicaAccountRoot | null>
anonymousAccountRoot
} = await anonymousAccount: MusicaAccountanonymousAccount.
Account.ensureLoaded<MusicaAccount, {
    root: {
        rootPlaylist: {
            tracks: {
                $each: boolean;
            };
        };
    };
}>(this: MusicaAccount, options: {
    resolve: RefsToResolve<MusicaAccount, 10, []>;
}): Promise<...>
@categorySubscription & Loading
ensureLoaded
({
resolve: RefsToResolve<MusicaAccount, 10, []>resolve: { root?: RefsToResolve<MusicaAccountRoot, 10, [0]> | undefinedroot: { rootPlaylist?: RefsToResolve<Playlist, 10, [0, 0]> | undefinedrootPlaylist: { tracks?: RefsToResolve<ListOfTracks, 10, [0, 0, 0]> | undefinedtracks: { $each: RefsToResolve<MusicTrack, 10, [0, 0, 0, 0]>$each: true, }, }, }, }, }); const
const me: {
    root: {
        rootPlaylist: {
            tracks: ListOfTracks;
        } & Playlist;
    } & MusicaAccountRoot;
} & MusicaAccount
me
= await class MusicaAccountMusicaAccount.Account.getMe<MusicaAccount>(this: CoValueClass<MusicaAccount> & typeof Account): MusicaAccountgetMe().
Account.ensureLoaded<MusicaAccount, {
    root: {
        rootPlaylist: {
            tracks: boolean;
        };
    };
}>(this: MusicaAccount, options: {
    resolve: RefsToResolve<MusicaAccount, 10, []>;
}): Promise<...>
@categorySubscription & Loading
ensureLoaded
({
resolve: RefsToResolve<MusicaAccount, 10, []>resolve: { root?: RefsToResolve<MusicaAccountRoot, 10, [0]> | undefinedroot: { rootPlaylist?: RefsToResolve<Playlist, 10, [0, 0]> | undefinedrootPlaylist: { tracks?: RefsToResolve<ListOfTracks, 10, [0, 0, 0]> | undefinedtracks: true, }, }, }, }); for (const const track: MusicTrack & co<MusicTrack | null>track of
const anonymousAccountRoot: {
    rootPlaylist: {
        tracks: MusicTrack[] & ListOfTracks;
    } & Playlist;
} & MusicaAccountRoot & co<MusicaAccountRoot | null>
anonymousAccountRoot
.
MusicaAccountRoot.rootPlaylist: {
    tracks: MusicTrack[] & ListOfTracks;
} & Playlist & co<Playlist | null>
rootPlaylist
.Playlist.tracks: MusicTrack[] & ListOfTracks & co<ListOfTracks | null>tracks) {
if (const track: MusicTrack & co<MusicTrack | null>track.MusicTrack.isExampleTrack: co<boolean | undefined>isExampleTrack) continue; const const trackGroup: GrouptrackGroup = const track: MusicTrack & co<MusicTrack | null>track.CoValueBase._owner: Account | Group
@categoryCollaboration
_owner
.CoValueBase.castAs<typeof Group>(cl: typeof Group): Group
@categoryType Helpers
castAs
(class Group
@categoryIdentity & Permissions
Group
);
const trackGroup: GrouptrackGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)addMember(
const me: {
    root: {
        rootPlaylist: {
            tracks: ListOfTracks;
        } & Playlist;
    } & MusicaAccountRoot;
} & MusicaAccount
me
, "admin");
const me: {
    root: {
        rootPlaylist: {
            tracks: ListOfTracks;
        } & Playlist;
    } & MusicaAccountRoot;
} & MusicaAccount
me
.
MusicaAccount.root: {
    rootPlaylist: {
        tracks: ListOfTracks;
    } & Playlist;
} & MusicaAccountRoot & co<MusicaAccountRoot | null>
root
.
MusicaAccountRoot.rootPlaylist: {
    tracks: ListOfTracks;
} & Playlist & co<Playlist | null>
rootPlaylist
.Playlist.tracks: ListOfTracks & co<ListOfTracks | null>tracks.CoList<Item = any>.push(...items: co<MusicTrack | null>[]): number
Appends new elements to the end of an array, and returns the new length of the array.
@paramitems New elements to add to the array.
push
(const track: MusicTrack & co<MusicTrack | null>track);
} }

To see how this works, try uploading a song in the music player demo and then log in with an existing account.

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 Account
  • when: "signedUp": Sync is enabled when the user is authenticated
  • when: "never": Sync is disabled, content stays local

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.

Configuring Guest Mode Access

You can configure Guest Mode access with the guestMode prop for Providers.

For more complex behaviours, you can manually control sync by statefully switching when between "always" and "never".