GrowthMRIGrowthMRI
DocsBlogChangelog
Developer/Authentication

Components

  • Auth Components
  • Contact Components
  • Dialog Manager
  • Layout Components
  • Markdown Components

Configuration

  • File Upload Adapters

Developer

  • Safe Actions
  • Zod Route
  • Authentication

Guide

  • Getting Started

Authentication

How the GrowthMRI auth flows work (signup, login, logout, password reset, email verification) and how to use them

Authentication is powered by Better Auth and is already complete: email/password, OAuth (GitHub/Google), email OTP / magic link, organizations, admin, password reset and email verification all ship out of the box. There is no custom auth code to write — this page documents how the existing flows work and how to call them.

  • Server config: src/lib/auth.ts
  • Browser client: src/lib/auth-client.ts
  • API endpoint: app/api/auth/[...auth]/route.ts (Better Auth catch-all)
  • UI pages: app/auth/*
  • DB models: prisma/schema/better-auth.prisma (User, Account, Session, Verification)

Passwords are never stored in plain text — Better Auth hashes them (scrypt) and stores the hash on Account.password for the credential provider.

Flows

Signup

  • Page: /auth/signup → app/auth/signup/sign-up-credentials-form.tsx
  • Call: authClient.signUp.email({ email, password, name, image })
  • Password match is enforced by signup.schema.ts (.refine).
  • On success the user is created, an organization is auto-provisioned (Better Auth user-create hook in auth.ts + the organization plugin), and the browser is sent to /orgs.
  • Email verification is not required to sign in and is not sent on signup (no requireEmailVerification / sendOnSignUp).
await authClient.signUp.email({ email, password, name });

Login

  • Page: /auth/signin → sign-in-password-form.tsx
  • Call: authClient.signIn.email({ email, password, rememberMe: true })
  • Sessions last 20 days and refresh every 7 (auth.ts session).

Login by OTP / magic link

  • Page: /auth/signin (OTP form) and /auth/signin/otp
  • Call: authClient.emailOtp.sendVerificationOtp(...) then authClient.signIn.emailOtp(...)
  • Backed by the emailOTP plugin in auth.ts.

Logout

  • Call: authClient.signOut() (re-exported from src/lib/auth-client.ts)
  • Endpoint: POST /api/auth/sign-out

Password reset (email OTP)

  • Page: /auth/forget-password → forget-password-page.tsx, a 3-step flow:
    1. authClient.forgetPassword.emailOtp({ email }) — sends a 6-digit OTP.
    2. authClient.emailOtp.checkVerificationOtp({ email, type: "forget-password", otp })
    3. authClient.emailOtp.resetPassword({ email, otp, password })
  • This is the only reset path. The legacy link-based sendResetPassword was removed because no page consumed it.

Email verification

  • Trigger (UI): account settings (/account → edit-profile-form.tsx) shows a Verify email button when emailVerified is false.
  • Call: authClient.sendVerificationEmail({ email })
  • The email contains a link to /api/auth/verify-email?token=…. The token is a stateless JWT signed with BETTER_AUTH_SECRET (it is not stored in the DB). Visiting the link flips User.emailVerified to true.

Server-side access

Use the helpers (see .claude/rules/authentication.md):

import { getUser, getRequiredUser } from "@/lib/auth/auth-user";
import { getCurrentOrg, getRequiredCurrentOrg } from "@/lib/organizations/get-org";

const user = await getUser();            // null if signed out
const user = await getRequiredUser();    // throws if signed out
const org = await getRequiredCurrentOrg();

In client components:

import { useSession } from "@/lib/auth-client";
const session = useSession();

Extending auth

Add a custom field to User

  1. Declare it under user.additionalFields in src/lib/auth.ts.
  2. Run pnpm better-auth:migrate to regenerate prisma/schema/better-auth.prisma.
  3. Apply the migration to the database.

Roles & permissions

  • App-level roles use the admin plugin (User.role, banned, …).
  • Organization roles/permissions are defined in src/lib/auth/auth-permissions.ts (ac, roles) and passed to the organization plugin in auth.ts.

Required environment variables

Copy .env-template to .env and fill at least:

  • DATABASE_URL (+ DATABASE_URL_UNPOOLED) — Postgres
  • BETTER_AUTH_SECRET — session/JWT signing (also required by the e2e email-verification test)
  • Email provider (Resend) — needed for reset/verification emails to actually send
  • Optional OAuth: GITHUB_CLIENT_ID/SECRET, GOOGLE_CLIENT_ID/SECRET

Testing

End-to-end tests live in e2e/ and run with pnpm test:e2e:ci (needs a running app + DB):

SpecCovers
e2e/signup.spec.tssignup, password hashing, org provisioning, password-match guard
e2e/login.spec.tslogin success + invalid password
e2e/logout.spec.tssession ends, protected pages redirect
e2e/reset-password.spec.tsfull OTP reset; old password rejected
e2e/email-verification.spec.tssend + verify via link

Because e2e has no inbox, helpers in e2e/utils/verification.ts read the reset OTP from the verification table and mint the verification JWT from BETTER_AUTH_SECRET.

Manual test checklist

  1. Sign up → redirected to /orgs/<slug>; user + org exist in DB.
  2. Log out → /orgs redirects to /auth/signin.
  3. Log in with the same credentials → back to /orgs.
  4. Forget password → receive OTP email → set a new password → sign in with it.
  5. /account → Verify email → open the emailed link → badge shows "verified".
Zod RouteGetting Started

On This Page

FlowsSignupLoginLogin by OTP / magic linkLogoutPassword reset (email OTP)Email verificationServer-side accessExtending authAdd a custom field to `User`Roles & permissionsRequired environment variablesTestingManual test checklist

GrowthMRI

Scannez votre site ou votre offre comme une IRM. Obtenez un diagnostic de croissance noté sur 100 et des actions concrètes pour convertir plus.

Product

DashboardAccount

Company

Contact

© 2026 GrowthMRI. All rights reserved.

Sign in