Better Auth authentication

Better Auth is a self-hosted, framework-agnostic authentication and authorisation framework for TypeScript.

You can integrate Better Auth with your Jazz app, allowing your Jazz user's account keys to be saved with the corresponding Better Auth user.

How it works

When using Better Auth authentication:

  1. Users sign up or sign in through Better Auth's authentication system
  2. Jazz securely stores the user's account keys with Better Auth
  3. When logging in, Jazz retrieves these keys from Better Auth
  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.

Authentication methods and plugins

Better Auth supports several authentication methods and plugins. The Jazz plugin has not been tested with all of them yet. Here is the compatibility matrix:

Better Auth method/pluginJazz plugin
Email/Password
Social Providers
Username
Anonymous
Phone Number
Magic Link
Email OTP
Passkey
One Tap

✅: tested and working ❓: not tested yet ❌: not supported

Getting started

First of all, follow the Better Auth documentation to install Better Auth:

  • Install the dependency and set env variables
  • Create the betterAuth instance in the common auth.ts file, using the database adapter you want.
  • Set up the authentication methods you want to use
  • Mount the handler in the API route
  • Create the client instance in the common auth-client.ts file

The jazz-tools/better-auth/auth plugin provides both server-side and client-side integration for Better Auth with Jazz. Here's how to set it up:

Server Setup

Add the jazzPlugin to the Better Auth instance:

// src/lib/auth.ts
import { betterAuth } from "better-auth";
import { jazzPlugin } from "jazz-tools/better-auth/auth/server";

// Your Better Auth server configuration
export const auth = betterAuth({
  // Add the Jazz plugin
  plugins: [
    jazzPlugin(),
    // other server plugins
  ],

  // rest of the Better Auth configuration
  // like database, email/password authentication, social providers, etc.
});

Now run migrations to add the new fields to the users table.

Note

The server-side plugin intercepts the custom header x-jazz-auth sent by client-side plugin. If server is behind a proxy, the header must be forwarded. If the server runs on a different origin than the client, the header must be allowed for cross-origin requests.

Client Setup

Create the Better Auth client with the Jazz plugin:

// src/lib/auth-client.ts
"use client";

import { createAuthClient } from "better-auth/client";
import { jazzPluginClient } from "jazz-tools/better-auth/auth/client";

export const betterAuthClient = createAuthClient({
  plugins: [
    jazzPluginClient(),
    // other client plugins
  ],
});

Wrap your app with the AuthProvider, passing the betterAuthClient instance:

// src/App.tsx
"use client";

import { AuthProvider } from "jazz-tools/better-auth/auth/react";
import { betterAuthClient } from "@/lib/auth-client";


export function App() {
  return (
    /* Other providers (e.g. your Jazz Provider) */
    <AuthProvider betterAuthClient={betterAuthClient}>
      {/* your app */}
    </AuthProvider>
  );
}

Authentication methods

The Jazz plugin intercepts the Better Auth client's calls, so you can use the Better Auth methods as usual.

Here is how to sign up with email and password, and transform an anonymous Jazz account into a logged in user authenticated by Better Auth:

import { betterAuthClient } from "@/lib/auth-client";

await betterAuthClient.signUp.email(
  {
    email: "email@example.com",
    password: "password",
    name: "John Doe",
  },
  {
    onSuccess: async () => {
      // Don't forget to update the profile's name. It's not done automatically.
      if (account?.me?.profile) {
        account.me.profile.name = "John Doe";
      }
    },
  }
);

You can then use the signIn and signOut methods on the betterAuthClient:

import { betterAuthClient } from "@/lib/auth-client";

await betterAuthClient.signIn.email({
  email: "email@example.com",
  password: "password",
});

await betterAuthClient.signOut();

Authentication states

Although Better Auth is not fully local-first, the Jazz client plugin tries to keep Jazz's authentication state in sync with Better Auth's. The best practice to check if the user is authenticated is using Jazz's methods as described here.

You can use Better Auth's native methods if you need to check the Better Auth state directly.

Server-side hooks

Better Auth provides database hooks to run code when things happen. When using the Jazz, the user's Jazz account ID is always available in the user object. This means you can access it anywhere in Better Auth hooks.

import { betterAuth } from "better-auth";
import { jazzPlugin } from "jazz-tools/better-auth/auth/server";

export const auth = betterAuth({
  plugins: [jazzPlugin()],
  databaseHooks: {
    user: {
      create: {
        async after(user) {
          // Here we can send a welcome email to the user
          console.log("User created with Jazz Account ID:", user.accountID);
        },
      },
    },
  },
});