Introduction

OverviewPhilosophyStructureUpdatesFAQ

Usage

Other

Switch to Better Auth

How to change the authentication provider to Better Auth.

Better Auth is a comprehensive, open-source authentication framework for TypeScript. It is designed to be framework agnostic, but integrates well with Next.js and provides a lot of features out of the box.

1. Swap out the auth package dependencies

Uninstall the existing Clerk dependencies from the auth package...

npm uninstall @clerk/nextjs @clerk/themes @clerk/types --filter @repo/auth

...and install the Better Auth dependencies:

npm install better-auth next --filter @repo/auth

Additionally, add @repo/database to the auth package dependencies.

2. Update your environment variables

Generate a secret with the following command to add it to the .env.local file in each Next.js application (app, web and api):

Terminal
npx @better-auth/cli@latest secret

This will add a BETTER_AUTH_SECRET environment variable to the .env.local file. You should also add the BETTER_AUTH_URL environment variable, pointing to your app's base URL:

.env.local
BETTER_AUTH_URL="http://localhost:3000"

3. Setup the server and client auth

Update the auth package files with the following code:

Server

packages/auth/server.ts
import { betterAuth } from 'better-auth';
import { nextCookies } from "better-auth/next-js";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { database } from "@repo/database"

export const auth = betterAuth({
  database: prismaAdapter(database, {
    provider: 'postgresql',
  }),
  plugins: [
    nextCookies()
    // organization() // if you want to use organization plugin
  ],
  //...add more options here
});

Client

packages/auth/client.ts
import { createAuthClient } from 'better-auth/react';

export const { signIn, signOut, signUp, useSession } = createAuthClient();

Read more in the Better Auth installation guide.

4. Update the auth components

Update both the sign-in.tsx and sign-up.tsx components in the auth package to use the signIn and signUp functions from the client file.

Sign In

packages/auth/components/sign-in.tsx
"use client";

import { signIn } from '../client';
import { useState } from 'react';

export const SignIn = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        await signIn.email({
          email,
          password,
        })
      }}
    >
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">Sign in</button>
    </form>
  );
}

Sign Up

packages/auth/components/sign-up.tsx
"use client";

import { signUp } from '../client';
import { useState } from 'react';

export const SignUp = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [name, setName] = useState("");

  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        await signUp.email({
          email,
          password,
          name
        })
      }}
    >
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <button type="submit">Sign up</button>
    </form>
  );
}

You can use different sign-in methods like social providers, phone, username etc. Read more about Better Auth basic usage.

5. Generate Prisma Models

From the root folder, generate Prisma models for Better Auth by running the following command:

Terminal
npx @better-auth/cli@latest generate --output ./packages/database/prisma/schema.prisma --config ./packages/auth/server.ts

You may have to comment out the server-only directive in packages/database/index.ts temporarily. Ensure you have environment variables set.

6. Update the Provider file

Better Auth has no concept of a Provider as a higher-order component, so you can either remove it entirely or just replace it with a stub, like so:

packages/auth/provider.tsx
import type { ReactNode } from 'react';

type AuthProviderProps = {
  children: ReactNode;
};

export const AuthProvider = ({ children }: AuthProviderProps) => children;

7. Change Middleware

Change the middleware in the auth package to the following. The middleware checks for a session cookie and redirects unauthenticated users to the sign-in page. The optional middlewareFn parameter allows you to add custom logic before the authentication check:

packages/auth/middleware.ts
import { getSessionCookie } from "better-auth/cookies";
import type { NextFetchEvent, NextRequest } from "next/server";
import { NextResponse } from "next/server";

export function authMiddleware(
  middlewareFn?: (
    _auth: { req: NextRequest; authorized: boolean },
    request: NextRequest,
    event: NextFetchEvent,
  ) => Promise<Response> | Response,
) {
  return async function middleware(request: NextRequest, event: NextFetchEvent) {
    const sessionCookie = getSessionCookie(request);
    const authorized = Boolean(sessionCookie);

    if (middlewareFn) {
      const response = await middlewareFn(
        { req: request, authorized },
        request,
        event
      );
      if (response && response.headers.get("Location")) {
        return response;
      }
    }

    if (!sessionCookie) {
      return NextResponse.redirect(new URL("/sign-in", request.url));
    }

    return NextResponse.next();
  };
}

8. Define and add Next.js Handlers to your app

Unlike Clerk, you need to host auth handlers which will retrieve sessions, authenticate requests etc...

packages/auth/handlers.ts
import 'server-only';
import { toNextJsHandler } from 'better-auth/next-js';

import { auth } from './server';

export const { POST, GET } = toNextJsHandler(auth);
apps/app/app/api/auth/[...all]/route.ts
export { POST, GET } from '@repo/auth/handlers'

9. Update your apps

From here, you'll need to replace any remaining Clerk implementations in your apps with Better Auth.

Here is some inspiration:

const user = await currentUser();
const { redirectToSignIn } = await auth();

// to

const session = await auth.api.getSession({
  headers: await headers(), // from next/headers
});
if (!session?.user) {
  return redirect('/sign-in'); // from next/navigation
}
// do something with session.user
const { orgId } = await auth();

// to

const h = await headers(); // from next/headers
const session = await auth.api.getSession({
  headers: h,
});
const orgId = session?.session.activeOrganizationId;
const fullOrganization = await auth.api.getFullOrganization({
  headers: h,
  query: { organizationId: orgId },
});
webhooks/stripe/route.ts
import { clerkClient } from '@repo/auth/server';

const clerk = await clerkClient();
const users = await clerk.users.getUserList();
const user = users.data.find(
  (user) => user.privateMetadata.stripeCustomerId === customerId
);

// to

import { database } from '@repo/database';

const user = await database.user.findFirst({
  where: {
    privateMetadata: {
      contains: { stripeCustomerId: customerId },
    },
  },
});

For using organization, check organization plugin and more from the Better Auth documentation.