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:

  1. Users sign up or sign in through Clerk's authentication system
  2. Jazz securely stores the user's account keys with Clerk
  3. When logging in, Jazz retrieves these keys from Clerk
  4. 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.
@function@returnsThe `useClerk()` hook returns the `Clerk` object, which includes all the methods and properties listed in the [`Clerk` reference](https://clerk.com/docs/references/javascript/clerk).@exampleThe following example uses the `useClerk()` hook to access the `clerk` object. The `clerk` object is used to call the [`openSignIn()`](https://clerk.com/docs/references/javascript/clerk#sign-in) method to open the sign-in modal. <Tabs items='React,Next.js'> <Tab> ```tsx {{ filename: 'src/Home.tsx' }} import { useClerk } from '@clerk/clerk-react' export default function Home() { const clerk = useClerk() return <button onClick={() => clerk.openSignIn({})}>Sign in</button> } ``` </Tab> <Tab> {@include ../../../docs/use-clerk.md#nextjs-01} </Tab> </Tabs>
useClerk
, function ClerkProvider(props: ClerkProviderProps): JSX.ElementClerkProvider, const ClerkLoaded: ({ children }: React.PropsWithChildren<unknown>) => React.ReactNodeClerkLoaded } 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 {
const JazzExpoProviderWithClerk: <S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>(props: {
    clerk: MinimalClerkClient;
} & JazzProviderProps<S>) => JSX.Element | null
JazzExpoProviderWithClerk
} from "jazz-tools/expo";
function
function JazzAndAuth({ children }: {
    children: React.ReactNode;
}): React.JSX.Element
JazzAndAuth
({ children: React.ReactNodechildren }: { children: React.ReactNodechildren: 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.
@see{@link https://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet}@example```tsx // Typing children type Props = { children: ReactNode } const Component = ({ children }: Props) => <div>{children}</div> <Component>hello</Component> ```@example```tsx // Typing a custom element type Props = { customElement: ReactNode } const Component = ({ customElement }: Props) => <div>{customElement}</div> <Component customElement={<div>hello</div>} /> ```
ReactNode
}) {
const const clerk: LoadedClerkclerk = 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.
@function@returnsThe `useClerk()` hook returns the `Clerk` object, which includes all the methods and properties listed in the [`Clerk` reference](https://clerk.com/docs/references/javascript/clerk).@exampleThe following example uses the `useClerk()` hook to access the `clerk` object. The `clerk` object is used to call the [`openSignIn()`](https://clerk.com/docs/references/javascript/clerk#sign-in) method to open the sign-in modal. <Tabs items='React,Next.js'> <Tab> ```tsx {{ filename: 'src/Home.tsx' }} import { useClerk } from '@clerk/clerk-react' export default function Home() { const clerk = useClerk() return <button onClick={() => clerk.openSignIn({})}>Sign in</button> } ``` </Tab> <Tab> {@include ../../../docs/use-clerk.md#nextjs-01} </Tab> </Tabs>
useClerk
();
return ( <
const JazzExpoProviderWithClerk: <S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>(props: {
    clerk: MinimalClerkClient;
} & JazzProviderProps<S>) => JSX.Element | null
JazzExpoProviderWithClerk
clerk: MinimalClerkClientclerk={const clerk: LoadedClerkclerk} sync: SyncConfigsync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com"peer: `wss://cloud.jazz.tools/?key=${const apiKey: "you@example.com"apiKey}`, }} > {children: React.ReactNodechildren} </
const JazzExpoProviderWithClerk: <S extends (AccountClass<Account> & CoValueFromRaw<Account>) | AnyAccountSchema>(props: {
    clerk: MinimalClerkClient;
} & JazzProviderProps<S>) => JSX.Element | null
JazzExpoProviderWithClerk
>
); } export default function function RootLayout(): React.JSX.ElementRootLayout() { const const publishableKey: string | undefinedpublishableKey = var process: NodeJS.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.string | undefinedEXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
if (!const publishableKey: string | undefinedpublishableKey) { throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
(
"Missing Publishable Key. Please set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env", ); } return ( <function ClerkProvider(props: ClerkProviderProps): JSX.ElementClerkProvider tokenCache?: 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.
@seehttps://clerk.com/docs/quickstarts/expo#configure-the-token-cache-with-expo
tokenCache
={
const tokenCache: {
    getToken: (key: string) => Promise<null>;
    saveToken: (key: string, token: string) => Promise<void>;
    clearToken: (key: string) => Promise<void>;
}
tokenCache
}
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: stringpublishableKey}
__experimental_resourceCache?: (() => IStorage) | undefined
This cache is used to store the resources that Clerk fetches from the server when the network is offline.
@experimentalThis API is experimental and may change at any moment.
__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.ReactNodeClerkLoaded> <
function JazzAndAuth({ children }: {
    children: React.ReactNode;
}): React.JSX.Element
JazzAndAuth
>
<function MainScreen(): nullMainScreen /> </
function JazzAndAuth({ children }: {
    children: React.ReactNode;
}): React.JSX.Element
JazzAndAuth
>
</const ClerkLoaded: ({ children }: React.PropsWithChildren<unknown>) => React.ReactNodeClerkLoaded> </function ClerkProvider(props: ClerkProviderProps): JSX.ElementClerkProvider> ); }
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).
@unionReturnHeadings["Initialization", "Loaded"]@example### Check the current state of a sign-in The following example uses the `useSignIn()` hook to access the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state. <Tabs items='React,Next.js'> <Tab> ```tsx {{ filename: 'src/pages/SignInPage.tsx' }} import { useSignIn } from '@clerk/clerk-react' export default function SignInPage() { const { isLoaded, signIn } = useSignIn() if (!isLoaded) { // Handle loading state return null } return <div>The current sign-in attempt status is {signIn?.status}.</div> } ``` </Tab> <Tab> {@include ../../docs/use-sign-in.md#nextjs-01} </Tab> </Tabs>@example### Create a custom sign-in flow with `useSignIn()` The `useSignIn()` hook can also be used to build fully custom sign-in flows, if Clerk's prebuilt components don't meet your specific needs or if you require more control over the authentication flow. Different sign-in flows include email and password, email and phone codes, email links, and multifactor (MFA). To learn more about using the `useSignIn()` hook to create custom flows, see the [custom flow guides](https://clerk.com/docs/custom-flows/overview). ```empty```
useSignIn
} from "@clerk/clerk-expo";
import {
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;
}
useAccount
, function useIsAuthenticated(): booleanuseIsAuthenticated } from "jazz-tools/expo";
import { class ButtonButton, class TextText } from 'react-native'; export function function AuthButton(): React.JSX.ElementAuthButton() { const { const logOut: () => voidlogOut } =
useAccount<AccountClass<Account> | AnyAccountSchema, true>(AccountSchema?: AccountClass<Account> | AnyAccountSchema | undefined, options?: {
    ...;
} | undefined): {
    ...;
}
useAccount
();
const { 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).
@unionReturnHeadings["Initialization", "Loaded"]@example### Check the current state of a sign-in The following example uses the `useSignIn()` hook to access the [`SignIn`](https://clerk.com/docs/references/javascript/sign-in) object, which contains the current sign-in attempt status and methods to create a new sign-in attempt. The `isLoaded` property is used to handle the loading state. <Tabs items='React,Next.js'> <Tab> ```tsx {{ filename: 'src/pages/SignInPage.tsx' }} import { useSignIn } from '@clerk/clerk-react' export default function SignInPage() { const { isLoaded, signIn } = useSignIn() if (!isLoaded) { // Handle loading state return null } return <div>The current sign-in attempt status is {signIn?.status}.</div> } ``` </Tab> <Tab> {@include ../../docs/use-sign-in.md#nextjs-01} </Tab> </Tabs>@example### Create a custom sign-in flow with `useSignIn()` The `useSignIn()` hook can also be used to build fully custom sign-in flows, if Clerk's prebuilt components don't meet your specific needs or if you require more control over the authentication flow. Different sign-in flows include email and password, email and phone codes, email links, and multifactor (MFA). To learn more about using the `useSignIn()` hook to create custom flows, see the [custom flow guides](https://clerk.com/docs/custom-flows/overview). ```empty```
useSignIn
();
const const isAuthenticated: booleanisAuthenticated = function useIsAuthenticated(): booleanuseIsAuthenticated(); if (const isAuthenticated: booleanisAuthenticated) { return <class ButtonButton title: 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: () => voidlogOut()} />;
} const const 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;
const const signInAttempt: SignInResourcesignInAttempt = await const 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: stringidentifier: "you@example.com", password: stringpassword: "password", }); if (const signInAttempt: SignInResourcesignInAttempt.SignInResource.status: SignInStatus | null
The current status of the sign-in.
status
=== "complete") {
await const 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: SignInResourcesignInAttempt.SignInResource.createdSessionId: string | nullcreatedSessionId });
} }; return <class ButtonButton title: 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

Additional resources