Skip to Content

Auth

Shipmore Core uses a dual auth model: Payload’s built-in auth for the admin panel, and Better Auth  for frontend customer sign-in and sessions.

Overview

ConcernSystemCollectionURL
Admin loginPayload authusers/admin
Customer sign-inBetter Authcustomers/auth (per tenant)

Admin users manage tenants, pages, and products. Customers sign in on the frontend to access dashboards, make purchases, and manage subscriptions.

Better Auth setup

Better Auth is integrated via the payload-auth plugin, which bridges Better Auth with Payload’s database and collections.

Sign-in methods

The default customer auth UI ships with one fully working sign-in path, plus two more that are wired at the API level:

  • Google OAuth — social sign-in with Google. This is the only sign-in method fully working out of the box in the default UI.
  • Magic link — passwordless email sign-in. The button is in the default UI, but the link sender is a stub that logs the link instead of emailing it. Wire the magic-link sender to your email system before relying on it (see Email).
  • Email + password — enabled in the Better Auth config at the API level, but there is no email/password form in the default sign-in UI. Add your own form if you want to surface it.

Configuration

Key auth behaviors:

  • Origin-inferred base URL — Better Auth infers the base URL from each request’s host. This is critical for multi-tenant setups where each tenant has a different domain.
  • Session — cookie-based with a 5-minute cache for performance.
  • Account linking — enabled for Google and email-password. If a user signs in with Google and later with email using the same address, the accounts are linked.

Google OAuth

To enable Google sign-in:

  1. Go to the Google Cloud Console APIs & ServicesCredentials.
  2. Create an OAuth 2.0 Client ID (Web application type).
  3. Add Authorized redirect URIs for each tenant domain, e.g.:
    • http://localhost:3000/api/auth/callback/google (local dev)
    • https://mysite.com/api/auth/callback/google (production)
    • Add one redirect URI per tenant domain you want Google sign-in on.
  4. Set the env vars:
GOOGLE_CLIENT_ID=your-client-id GOOGLE_CLIENT_SECRET=your-client-secret NEXT_PUBLIC_GOOGLE_CLIENT_ID=your-client-id # used client-side

If these vars are not set, the Google sign-in button won’t appear.

Multi-tenant auth

Trusted origins

Better Auth needs to trust each tenant domain for CORS and cookie handling. The configuration automatically adds:

  • Origins from env vars (NEXT_PUBLIC_BETTER_AUTH_URL, BETTER_AUTH_URL, NEXT_PUBLIC_SERVER_URL).
  • The current request’s host — so any tenant domain is automatically trusted at runtime.

OAuth callbacks per tenant

For social sign-in (Google), the OAuth callback URL must match the domain the user is signing in from. A middleware in the auth configuration overrides the baseURL for /sign-in/social and /callback/* routes using the request’s Host header. This ensures the OAuth flow redirects back to the correct tenant domain.

In practice, this means Google OAuth works on any tenant domain as long as you’ve added the redirect URI for that domain in the Google Cloud Console.

Auth cookies are scoped to the domain they were set on. A customer signed in on tenant-a.com is not signed in on tenant-b.com. This is expected browser behavior and provides proper tenant isolation.

Session handling

Server-side

In server components, API routes, and server actions, read the session server-side via the auth domain’s session helper. It resolves the current customer from the request headers; session.user contains the authenticated customer.

Client-side

Client components use the Better Auth React client. Its base URL is inferred from the current origin, so it automatically points to the current tenant’s domain (useSession(), signIn, signOut, etc.).

Protected routes

Server actions are guarded by composable procedures that layer on top of each other:

  • A base auth guard — requires an active session; rejects unauthenticated calls.
  • A Stripe-customer guard — extends the auth guard and ensures the caller has a Stripe customer record.
  • A paywall guard — extends further with credit and subscription-tier checks.

Frontend pages like /dashboard check the session at the page or layout level and redirect unauthenticated users to /auth.