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: MusicaAccount
anonymousAccount:class MusicaAccount
MusicaAccount, ) { const {root:
MusicaAccount.root: { rootPlaylist: { tracks: MusicTrack[] & ListOfTracks; } & Playlist; } & MusicaAccountRoot & co<MusicaAccountRoot | null>
anonymousAccountRoot } = await
const anonymousAccountRoot: { rootPlaylist: { tracks: MusicTrack[] & ListOfTracks; } & Playlist; } & MusicaAccountRoot & co<MusicaAccountRoot | null>
anonymousAccount: MusicaAccount
anonymousAccount.ensureLoaded({
Account.ensureLoaded<MusicaAccount, { root: { rootPlaylist: { tracks: { $each: boolean; }; }; }; }>(this: MusicaAccount, options: { resolve: RefsToResolve<MusicaAccount, 10, []>; }): Promise<...>
resolve: RefsToResolve<MusicaAccount, 10, []>
resolve: {root?: RefsToResolve<MusicaAccountRoot, 10, [0]> | undefined
root: {rootPlaylist?: RefsToResolve<Playlist, 10, [0, 0]> | undefined
rootPlaylist: {tracks?: RefsToResolve<ListOfTracks, 10, [0, 0, 0]> | undefined
tracks: {$each: RefsToResolve<MusicTrack, 10, [0, 0, 0, 0]>
$each: true, }, }, }, }, }); constme = await
const me: { root: { rootPlaylist: { tracks: ListOfTracks; } & Playlist; } & MusicaAccountRoot; } & MusicaAccount
class MusicaAccount
MusicaAccount.Account.getMe<MusicaAccount>(this: CoValueClass<MusicaAccount> & typeof Account): MusicaAccount
getMe().ensureLoaded({
Account.ensureLoaded<MusicaAccount, { root: { rootPlaylist: { tracks: boolean; }; }; }>(this: MusicaAccount, options: { resolve: RefsToResolve<MusicaAccount, 10, []>; }): Promise<...>
resolve: RefsToResolve<MusicaAccount, 10, []>
resolve: {root?: RefsToResolve<MusicaAccountRoot, 10, [0]> | undefined
root: {rootPlaylist?: RefsToResolve<Playlist, 10, [0, 0]> | undefined
rootPlaylist: {tracks?: RefsToResolve<ListOfTracks, 10, [0, 0, 0]> | undefined
tracks: true, }, }, }, }); for (constconst track: MusicTrack & co<MusicTrack | null>
track ofanonymousAccountRoot.
const anonymousAccountRoot: { rootPlaylist: { tracks: MusicTrack[] & ListOfTracks; } & Playlist; } & MusicaAccountRoot & co<MusicaAccountRoot | null>
rootPlaylist.
MusicaAccountRoot.rootPlaylist: { tracks: MusicTrack[] & ListOfTracks; } & Playlist & co<Playlist | null>
Playlist.tracks: MusicTrack[] & ListOfTracks & co<ListOfTracks | null>
tracks) { if (const track: MusicTrack & co<MusicTrack | null>
track.MusicTrack.isExampleTrack: co<boolean | undefined>
isExampleTrack) continue; constconst trackGroup: Group
trackGroup =const track: MusicTrack & co<MusicTrack | null>
track.CoValueBase._owner: Account | Group
_owner.CoValueBase.castAs<typeof Group>(cl: typeof Group): Group
castAs(class Group
Group);const trackGroup: Group
trackGroup.Group.addMember(member: Account, role: AccountRole): void (+1 overload)
addMember(me, "admin");
const me: { root: { rootPlaylist: { tracks: ListOfTracks; } & Playlist; } & MusicaAccountRoot; } & MusicaAccount
me.
const me: { root: { rootPlaylist: { tracks: ListOfTracks; } & Playlist; } & MusicaAccountRoot; } & MusicaAccount
root.
MusicaAccount.root: { rootPlaylist: { tracks: ListOfTracks; } & Playlist; } & MusicaAccountRoot & co<MusicaAccountRoot | null>
rootPlaylist.
MusicaAccountRoot.rootPlaylist: { tracks: ListOfTracks; } & Playlist & co<Playlist | null>
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.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 Accountwhen: "signedUp"
: Sync is enabled when the user is authenticatedwhen: "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"
.