Skip to content

Authentication Setup

NuxtBase uses Better Auth and enables more than just email/password login.

From the template source, the auth layer includes:

  • email and password login
  • email verification
  • password reset
  • magic links
  • email OTP
  • Google and GitHub OAuth
  • two-factor authentication
  • passkeys
  • organization and admin plugins

This page focuses on the setup decisions you need to make before those features behave correctly.

The most important auth variables are:

Terminal window
BETTER_AUTH_URL=http://localhost:3000
NUXT_PUBLIC_SITE_URL=http://localhost:3000

In the template, both values are used to build the trusted origin list. If these URLs are wrong, auth callbacks, verification links, or browser-origin checks become unreliable.

For local development, keep them aligned:

Terminal window
BETTER_AUTH_URL=http://localhost:3000
NUXT_PUBLIC_SITE_URL=http://localhost:3000

For production, move both to your real app origin:

Terminal window
BETTER_AUTH_URL=https://app.example.com
NUXT_PUBLIC_SITE_URL=https://app.example.com

For the default auth stack to work, configure:

Terminal window
BETTER_AUTH_SECRET=replace-with-a-random-secret
BETTER_AUTH_URL=http://localhost:3000
NUXT_PUBLIC_SITE_URL=http://localhost:3000

BETTER_AUTH_SECRET must be long enough to pass validation. In this template, anything shorter than 32 characters is rejected.

NUXT_PUBLIC_APP_NAME is optional here. If you do not override it, the template falls back to the default app name from siteConfig.appName.

Even before you add OAuth, the template expects auth-related emails to work:

  • verification email
  • password reset email
  • magic link email
  • verification code email
  • organization invitation email

For local development, the default preview email driver is enough to make these flows usable without external email infrastructure.

That means you can finish auth setup first, then move to real email delivery later.

Google and GitHub login are optional. Each provider is enabled only if both of its env vars are present.

Provider dashboards:

Terminal window
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...

Local setup:

  1. Open Google Cloud Console.
  2. Create a new Google Cloud project for this app, or explicitly choose the existing project that already owns this app’s Google sign-in setup.
  3. Remember that one project only has one OAuth branding / consent screen configuration. Do not mix multiple unrelated apps under the same project unless they intentionally share the same Google auth brand.
  4. Create an OAuth 2.0 Client ID and choose Web application.
  5. Set Authorized JavaScript origins to http://localhost:3000.
  6. Set Authorized redirect URIs to http://localhost:3000/api/auth/callback/google.
  7. Copy the generated Client ID and Client Secret into .env.
  8. Restart your local dev server.

Production setup:

  1. Open the Google Cloud project that is dedicated to this app’s production Google sign-in, or create one if you do not want to share branding with another app.
  2. Review the OAuth branding / consent screen in that project and make sure it matches the production product name, logo, and support email you actually want users to see.
  3. Create a production OAuth 2.0 Client ID, or a separate production client inside that project.
  4. Set Authorized JavaScript origins to your real app origin, for example https://app.example.com.
  5. Set Authorized redirect URIs to your real callback URL, for example https://app.example.com/api/auth/callback/google.
  6. Put the production Client ID and Client Secret into your production environment variables.
  7. Make sure BETTER_AUTH_URL and NUXT_PUBLIC_SITE_URL use that same production app origin.
  8. Redeploy the app.
Terminal window
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...

Local setup:

  1. Open GitHub Developer Settings and go to OAuth Apps.
  2. Create a new OAuth App.
  3. Set Homepage URL to http://localhost:3000.
  4. Set Authorization callback URL to http://localhost:3000/api/auth/callback/github.
  5. Copy the generated Client ID and Client Secret into .env.
  6. Restart your local dev server.

Production setup:

  1. Open the same GitHub OAuth App, or create a separate production app.
  2. Set Homepage URL to your real app origin, for example https://app.example.com.
  3. Set Authorization callback URL to your real callback URL, for example https://app.example.com/api/auth/callback/github.
  4. Put the production Client ID and Client Secret into your production environment variables.
  5. Make sure BETTER_AUTH_URL and NUXT_PUBLIC_SITE_URL use that same production app origin.
  6. Redeploy the app.

If you configure only one half of a provider pair, startup fails.

When the auth handler is mounted at /api/auth, Better Auth uses these callback paths:

  • Google: /api/auth/callback/google
  • GitHub: /api/auth/callback/github

So if your app runs at https://app.example.com, register these full callback URLs with the provider dashboards:

https://app.example.com/api/auth/callback/google
https://app.example.com/api/auth/callback/github

Passkeys are enabled in the template through @better-auth/passkey.

BETTER_AUTH_PASSKEY_RP_ID and BETTER_AUTH_PASSKEY_RP_NAME define the Relying Party that the browser shows and validates during passkey registration and login.

These variables are optional:

Terminal window
BETTER_AUTH_PASSKEY_RP_ID=localhost
BETTER_AUTH_PASSKEY_RP_NAME=My SaaS

What they do:

  • BETTER_AUTH_PASSKEY_RP_ID is the domain identity for the passkey. Browsers and platform authenticators use it to decide whether a stored passkey is valid for the current site.
  • BETTER_AUTH_PASSKEY_RP_NAME is the human-readable app name shown to users in the passkey creation or sign-in prompt.

In practice:

  • if your app runs locally on http://localhost:3000, RP_ID is typically localhost
  • if your app runs on https://app.example.com, RP_ID should usually be app.example.com
  • RP_NAME should be the product name users recognize, for example NuxtBase or your own app name

These values matter because passkeys are origin-sensitive:

  • if BETTER_AUTH_PASSKEY_RP_ID does not match the real app hostname, passkey registration or login can fail
  • if BETTER_AUTH_PASSKEY_RP_NAME is misleading or stale, users may see the wrong product name in the browser or device passkey UI
  • if you change the final production domain later, you should review the passkey configuration again before relying on passkeys in production

If you do not set them:

  • BETTER_AUTH_PASSKEY_RP_ID falls back to the hostname from BETTER_AUTH_URL or NUXT_PUBLIC_SITE_URL
  • BETTER_AUTH_PASSKEY_RP_NAME falls back to NUXT_PUBLIC_APP_NAME

That default is good enough for many local and single-domain setups.

One useful behavior is easy to miss if you only look at the UI: after a user is created, the auth hooks automatically create a default personal organization and attach the user as owner.

That is why auth setup and organization setup are closely connected in NuxtBase.

  1. Set the base URLs and auth secret
  2. Start the app and verify email/password registration works
  3. Confirm verification emails or preview files are generated
  4. Add Google and GitHub only after the core flow is stable
  5. Configure passkeys after your final app domain is decided

After auth setup, verify:

  1. /register creates a user
  2. /login works with email and password
  3. verification or magic-link emails are generated
  4. /dashboard loads after login
  5. /dashboard/settings shows security settings, including passkeys