Skip to Content
Apso is in public beta. Get started
GuidesAuthBetter Auth

Better Auth

@apso/better-auth-adapter lets a Better Auth server store users, sessions, accounts, and verification records through an Apso-generated REST API. A common setup runs Better Auth in a Next.js backend-for-frontend and keeps the Apso API key on that server.

Architecture

Better Auth boundaryKeep the session close to the user and the service key on the server.
Server-side key
Browser

Product client

session cookie

Sign-up, sign-in, sign-out, and session requests go to the application's auth route.

Trusted server

Next.js + Better Auth

@apso/better-auth-adapter

The adapter applies the private service key and translates Better Auth operations.

Owned backend

Apso service + PostgreSQL

User • Session • Account

The generated REST resources persist identity records behind the server boundary.

The browser never receives APSO_API_KEY. Tenant authorization remains a separate application decision.

The browser communicates with the Better Auth route. The adapter makes server-side calls to Apso. The Apso service key never reaches the browser.

Prepare the Apso backend

The adapter expects generated resources for these Better Auth models:

EntityCore fields
Userid, email, emailVerified, name, image
Sessionid, token, userId, expiresAt, ipAddress, userAgent
Accountid, userId, providerId, providerAccountId, provider token fields
VerificationTokenid, identifier, value, expiresAt

Use exact field names because the adapter maps Better Auth operations to the plural Apso routes for these entities. Add User relationships to Session and Account, then generate and deploy the backend.

Create a service API key in the dashboard after deployment. Store the deployed URL and key in the Next.js server environment.

Install the server packages

npm install better-auth @apso/better-auth-adapter

Configure Better Auth

src/lib/auth.ts
import 'server-only'; import { apsoAdapter } from '@apso/better-auth-adapter'; import { betterAuth } from 'better-auth'; export const auth = betterAuth({ database: apsoAdapter({ baseUrl: process.env.APSO_API_URL!, apiKey: process.env.APSO_API_KEY!, }), emailAndPassword: { enabled: true, }, trustedOrigins: [process.env.APP_URL!], });
.env.local
APSO_API_URL=https://your-service.example.com APSO_API_KEY=replace-with-a-server-side-key APP_URL=http://localhost:3000 BETTER_AUTH_SECRET=replace-with-a-long-random-secret

Keep APSO_API_KEY private. Do not prefix it with NEXT_PUBLIC_.

Mount the Auth handler

src/app/api/auth/[...all]/route.ts
import { toNextJsHandler } from 'better-auth/next-js'; import { auth } from '@/lib/auth'; export const { GET, POST } = toNextJsHandler(auth);

Create the browser client

src/lib/auth-client.ts
import { createAuthClient } from 'better-auth/react'; export const authClient = createAuthClient({ baseURL: process.env.NEXT_PUBLIC_APP_URL, });
'use client'; import { authClient } from '@/lib/auth-client'; export function AccountMenu() { const { data: session, isPending } = authClient.useSession(); if (isPending) return null; if (!session) return <span>Signed out</span>; return ( <button onClick={() => authClient.signOut()}> Sign out {session.user.email} </button> ); }

Verify the flow

  1. Start the Apso service and the Next.js application.
  2. Sign up with a new email address.
  3. Confirm that records appear in Users, Accounts, and Sessions.
  4. Refresh a protected page and confirm that the session persists.
  5. Sign out and confirm that the session can no longer authorize the request.
  6. Test a second user and any tenant-isolation rule your application applies.

Better Auth handles identity and sessions. Your application still needs authorization rules for roles, workspaces, and record ownership.

Last updated on