nextauth → briven
moving from NextAuth (or Auth.js) to briven — which uses Better Auth under the hood. the protocols line up cleanly; the work is at the integration seam (your prisma adapter vs briven's schema, your getServerSession vs briven's client SDK). plan a 2-3 day window for a single-app cutover.
account preservation
two cutover shapes — pick one before you start:
- preserve user ids(recommended): import your nextauth users into briven's
userstable with the existing ids. all your foreign keys keep working (posts.userIdstill resolves). users sign in fresh once after cutover. covered in step 3 of the playbook. - preserve sessions: bridge nextauth sessions to briven for a window via a custom middleware. only worth it if you can't stomach a forced re-auth (consumer app with millions of active sessions). file a ticket — this path is supported but not self-service.
provider port
nextauth providers map to briven providers 1:1 — same OAuth endpoints, same scopes, same redirect URIs. you just register them on the briven side instead of in your nextauth config. supported on briven today:
- magic link via email — drop-in for nextauth's
EmailProvider. briven uses mittera.eu (configurable);sendMagicLinkalready wired. - email + password — drop-in for nextauth's
CredentialsProviderwhen you used password hashing. briven uses argon2id (better-auth default). - Google OAuth — register a new client at console.cloud.google.com with redirect URI
https://api.briven.tech/v1/auth/callback/google. - GitHub OAuth — same shape, redirect URI
https://api.briven.tech/v1/auth/callback/github.
not supported yet (file a ticket if you're blocked on one): Apple, Twitter, Facebook, Discord. the underlying Better Auth library covers all of these — they just need a small wire-up patch.
schema port
nextauth's users / accounts / sessions / verification_tokens tables map 1:1 to briven's users / accounts / sessions / verifications. column names line up exactly because both libraries target the same Better Auth schema shape. import script:
-- inside the briven control-plane meta-db, after a fresh briven install
INSERT INTO users (id, email, email_verified, name, image, created_at)
SELECT id, email, email_verified IS NOT NULL, name, image, created_at
FROM nextauth_dump.users;
INSERT INTO accounts (id, user_id, provider_id, account_id, access_token,
refresh_token, scope, id_token, password)
SELECT id, user_id, provider, "providerAccountId", access_token,
refresh_token, scope, id_token, NULL
FROM nextauth_dump.accounts;
-- sessions intentionally NOT imported; users sign in fresh after cutover.api shape
nextauth's getServerSession(authOptions)becomes briven's server-side session helper. nextauth's useSession()becomes briven's useSession() hook from @briven/react.
// before — nextauth on next.js
import { getServerSession } from 'next-auth';
export default async function Page() {
const session = await getServerSession(authOptions);
if (!session) return <SignInPrompt />;
return <Dashboard user={session.user} />;
}
// after — briven
import { brivenServer } from '@briven/react/server';
export default async function Page() {
const session = await brivenServer.session();
if (!session) return <SignInPrompt />;
return <Dashboard user={session.user} />;
}callback / event hooks
nextauth's callbacks.session, callbacks.jwt, and events.signIn become briven server functions called from the auth lifecycle. instead of mutating the session object in a callback, write a briven function that returns whatever shape your app needs and call it from your dashboard:
// before — nextauth callback
callbacks: {
async session({ session, user }) {
session.user.role = await getRoleFromDb(user.id);
return session;
},
}
// after — briven function the client calls after sign-in
// briven/functions/getCurrentUser.ts
import { query } from '@briven/cli/server';
export const getCurrentUser = query({
args: {},
handler: async (ctx) => {
if (!ctx.user) return null;
const role = await ctx.db('user_roles')
.select(['role'])
.where({ user_id: ctx.user.id })
.first();
return { ...ctx.user, role: role?.role ?? 'free' };
},
});cutover checklist
- users + accounts imported, row counts match nextauth source
- every nextauth provider re-registered with briven callback URLs
- sign-in flow tested for every provider (manual)
- getServerSession call sites replaced with brivenServer.session()
- useSession imports updated to @briven/react
- nextauth API routes (/api/auth/*) removed
- session secret rotated (do not reuse nextauth's NEXTAUTH_SECRET)
- parallel-run window planned + observed