Clerk Authentication
Jazz can be integrated with Clerk to authenticate users. This method combines Clerk's comprehensive authentication services with Jazz's local-first capabilities.
How it works
When using Clerk authentication:
- Users sign up or sign in through Clerk's authentication system
- Jazz securely stores the user's account keys with Clerk
- When logging in, Jazz retrieves these keys from Clerk
- Once authenticated, users can work offline with full Jazz functionality
This authentication method is not fully local-first, as login and signup need to be done online, but once authenticated, users can use all of Jazz's features without needing to be online.
Key benefits
- Rich auth options: Email/password, social logins, multi-factor authentication
- User management: Complete user administration dashboard
- Familiar sign-in: Standard auth flows users already know
- OAuth providers: Google, GitHub, and other popular providers
- Enterprise features: SSO, SAML, and other advanced options
Implementation
Use <JazzExpoProviderWithClerk />
to wrap your app.
import {
const useClerk: () => LoadedClerk
> [!WARNING] > This hook should only be used for advanced use cases, such as building a completely custom OAuth flow or as an escape hatch to access to the `Clerk` object. The `useClerk()` hook provides access to the [`Clerk`](https://clerk.com/docs/references/javascript/clerk) object, allowing you to build alternatives to any Clerk Component.useClerk,function ClerkProvider(props: ClerkProviderProps): JSX.Element
ClerkProvider,const ClerkLoaded: ({ children }: React.PropsWithChildren<unknown>) => React.ReactNode
ClerkLoaded } from '@clerk/clerk-expo'; import {const resourceCache: () => IStorage
Creates a store based on expo-secure-store, that handles the 2048 size limit on values. The store uses a queue to manage multiple save requests and two slots (A and B) to store the key-value pairs. The function alternates between the two slots to save the key-value pairs and splits the value into chunks to save them. The two slots are used to handle corrupted data or incomplete saves. The keys used are the following: - key-latest -> 'A'/'B' - key-{A/B}-metadata -> Metadata - key-{A/B}-chunk-{i} -> data chunk - key-{A/B}-complete -> 'true'/'false'resourceCache } from '@clerk/clerk-expo/resource-cache'; import {JazzExpoProviderWithClerk } from "jazz-tools/expo"; function
const JazzExpoProviderWithClerk: <S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>(props: { clerk: MinimalClerkClient; } & JazzProviderProps<S>) => JSX.Element | null
JazzAndAuth({
function JazzAndAuth({ children }: { children: React.ReactNode; }): React.JSX.Element
children: React.ReactNode
children }: {children: React.ReactNode
children: React.type React.ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal | Promise<...> | null | undefined
Represents all of the things React can render. Where {@link ReactElement } only represents JSX, `ReactNode` represents everything that can be rendered.ReactNode }) { constconst clerk: LoadedClerk
clerk =function useClerk(): LoadedClerk
> [!WARNING] > This hook should only be used for advanced use cases, such as building a completely custom OAuth flow or as an escape hatch to access to the `Clerk` object. The `useClerk()` hook provides access to the [`Clerk`](https://clerk.com/docs/references/javascript/clerk) object, allowing you to build alternatives to any Clerk Component.useClerk(); return ( <JazzExpoProviderWithClerk
const JazzExpoProviderWithClerk: <S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>(props: { clerk: MinimalClerkClient; } & JazzProviderProps<S>) => JSX.Element | null
clerk: MinimalClerkClient
clerk={const clerk: LoadedClerk
clerk}sync: SyncConfig
sync={{peer: "wss://cloud.jazz.tools/?key=you@example.com"
peer: `wss://cloud.jazz.tools/?key=${const apiKey: "you@example.com"
apiKey}`, }} > {children: React.ReactNode
children} </JazzExpoProviderWithClerk> ); } export default function
const JazzExpoProviderWithClerk: <S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>(props: { clerk: MinimalClerkClient; } & JazzProviderProps<S>) => JSX.Element | null
function RootLayout(): React.JSX.Element
RootLayout() { constconst publishableKey: string | undefined
publishableKey =var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment. See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html). An example of this object looks like: ```js { TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node' } ``` It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other `Worker` threads. In other words, the following example would not work: ```bash node -e 'process.env.foo = "bar"' && echo $foo ``` While the following will: ```js import { env } from 'node:process'; env.foo = 'bar'; console.log(env.foo); ``` Assigning a property on `process.env` will implicitly convert the value to a string. **This behavior is deprecated.** Future versions of Node.js may throw an error when the value is not a string, number, or boolean. ```js import { env } from 'node:process'; env.test = null; console.log(env.test); // => 'null' env.test = undefined; console.log(env.test); // => 'undefined' ``` Use `delete` to delete a property from `process.env`. ```js import { env } from 'node:process'; env.TEST = 1; delete env.TEST; console.log(env.TEST); // => undefined ``` On Windows operating systems, environment variables are case-insensitive. ```js import { env } from 'node:process'; env.TEST = 1; console.log(env.test); // => 1 ``` Unless explicitly specified when creating a `Worker` instance, each `Worker` thread has its own copy of `process.env`, based on its parent thread's `process.env`, or whatever was specified as the `env` option to the `Worker` constructor. Changes to `process.env` will not be visible across `Worker` threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner unlike the main thread.env.string | undefined
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY; if (!const publishableKey: string | undefined
publishableKey) { throw newError( "Missing Publishable Key. Please set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env", ); } return ( <
var Error: ErrorConstructor new (message?: string, options?: ErrorOptions) => Error (+1 overload)
function ClerkProvider(props: ClerkProviderProps): JSX.Element
ClerkProvidertokenCache?: TokenCache | undefined
The token cache is used to persist the active user's session token. Clerk stores this token in memory by default, however it is recommended to use a token cache for production applications.tokenCache={tokenCache}
const tokenCache: { getToken: (key: string) => Promise<null>; saveToken: (key: string, token: string) => Promise<void>; clearToken: (key: string) => Promise<void>; }
publishableKey?: string | undefined
Used to override the default EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY env variable if needed. This is optional for Expo as the ClerkProvider will automatically use the EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY env variable if it exists.publishableKey={const publishableKey: string
publishableKey}__experimental_resourceCache?: (() => IStorage) | undefined
This cache is used to store the resources that Clerk fetches from the server when the network is offline.__experimental_resourceCache={const resourceCache: () => IStorage
Creates a store based on expo-secure-store, that handles the 2048 size limit on values. The store uses a queue to manage multiple save requests and two slots (A and B) to store the key-value pairs. The function alternates between the two slots to save the key-value pairs and splits the value into chunks to save them. The two slots are used to handle corrupted data or incomplete saves. The keys used are the following: - key-latest -> 'A'/'B' - key-{A/B}-metadata -> Metadata - key-{A/B}-chunk-{i} -> data chunk - key-{A/B}-complete -> 'true'/'false'resourceCache} > <const ClerkLoaded: ({ children }: React.PropsWithChildren<unknown>) => React.ReactNode
ClerkLoaded> <JazzAndAuth> <
function JazzAndAuth({ children }: { children: React.ReactNode; }): React.JSX.Element
function MainScreen(): null
MainScreen /> </JazzAndAuth> </
function JazzAndAuth({ children }: { children: React.ReactNode; }): React.JSX.Element
const ClerkLoaded: ({ children }: React.PropsWithChildren<unknown>) => React.ReactNode
ClerkLoaded> </function ClerkProvider(props: ClerkProviderProps): JSX.Element
ClerkProvider> ); }
import {
const useSignIn: () => UseSignInReturn
The `useSignIn()` hook provides access to the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in) object, which allows you to check the current state of a sign-in attempt and manage the sign-in flow. You can use this to create a [custom sign-in flow](https://clerk.com/docs/custom-flows/overview#sign-in-flow).useSignIn } from "@clerk/clerk-expo"; import {useAccount,
function useAccount<A extends AccountClass<Account> | AnyAccountSchema, R extends ResolveQuery<A> = true>(AccountSchema?: A, options?: { resolve?: ResolveQueryStrict<A, R>; }): { me: Loaded<A, R> | undefined | null; agent: AnonymousJazzAgent | Loaded<A, true>; logOut: () => void; }
function useIsAuthenticated(): boolean
useIsAuthenticated } from "jazz-tools/expo"; import {class Button
Button,class Text
Text } from 'react-native'; export functionfunction AuthButton(): React.JSX.Element
AuthButton() { const {const logOut: () => void
logOut } =useAccount(); const {
useAccount<AccountClass<Account> | AnyAccountSchema, true>(AccountSchema?: AccountClass<Account> | AnyAccountSchema | undefined, options?: { ...; } | undefined): { ...; }
const signIn: SignInResource | undefined
An object that contains the current sign-in attempt status and methods to create a new sign-in attempt.signIn,const setActive: SetActive | undefined
A function that sets the active session.setActive,const isLoaded: boolean
A boolean that indicates whether Clerk has completed initialization. Initially `false`, becomes `true` once Clerk loads.isLoaded } =function useSignIn(): UseSignInReturn
The `useSignIn()` hook provides access to the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in) object, which allows you to check the current state of a sign-in attempt and manage the sign-in flow. You can use this to create a [custom sign-in flow](https://clerk.com/docs/custom-flows/overview#sign-in-flow).useSignIn(); constconst isAuthenticated: boolean
isAuthenticated =function useIsAuthenticated(): boolean
useIsAuthenticated(); if (const isAuthenticated: boolean
isAuthenticated) { return <class Button
Buttontitle: string
Text to display inside the button. On Android the given title will be converted to the uppercased form.title="Logout"onPress?: ((event: GestureResponderEvent) => void) | undefined
Called when the touch is released, but not if cancelled (e.g. by a scroll that steals the responder lock).onPress={() =>const logOut: () => void
logOut()} />; } constconst onSignInPress: () => Promise<void>
onSignInPress = async () => { if (!const isLoaded: boolean
A boolean that indicates whether Clerk has completed initialization. Initially `false`, becomes `true` once Clerk loads.isLoaded) return; constconst signInAttempt: SignInResource
signInAttempt = awaitconst signIn: SignInResource
An object that contains the current sign-in attempt status and methods to create a new sign-in attempt.signIn.SignInResource.create: (params: SignInCreateParams) => Promise<SignInResource>
create({identifier: string
identifier: "you@example.com",password: string
password: "password", }); if (const signInAttempt: SignInResource
signInAttempt.SignInResource.status: SignInStatus | null
The current status of the sign-in.status === "complete") { awaitconst setActive: (setActiveParams: SetActiveParams) => Promise<void>
A function that sets the active session.setActive({session?: string | SignedInSessionResource | null | undefined
The session resource or session ID (string version) to be set as active. If `null`, the current session is deleted.session:const signInAttempt: SignInResource
signInAttempt.SignInResource.createdSessionId: string | null
createdSessionId }); } }; return <class Button
Buttontitle: string
Text to display inside the button. On Android the given title will be converted to the uppercased form.title="Sign In"onPress?: ((event: GestureResponderEvent) => void) | undefined
Called when the touch is released, but not if cancelled (e.g. by a scroll that steals the responder lock).onPress={const onSignInPress: () => Promise<void>
onSignInPress} />; }
Examples
You can explore Jazz with Clerk integration in our example projects. For more Clerk-specific demos, visit Clerk's documentation.
When to use Clerk
Clerk authentication is ideal when:
- You need an existing user management system
- You want to integrate with other Clerk features (roles, permissions)
- You require email/password authentication with verification
- You need OAuth providers (Google, GitHub, etc.)
- You want to avoid users having to manage passphrases
Limitations and considerations
- Online requirement: Initial signup/login requires internet connectivity
- Third-party dependency: Relies on Clerk's services for authentication
- Not fully local-first: Initial authentication requires a server
- Platform support: Not available on all platforms