Nextbase Docs
Nextbase Docs
HomeBlogWelcome to Nextbase v5!Choose your starterGetting startedLocal Supabase per agent
Next.js + ConvexInstallationEnvironment variablesAuthenticationAgent connectionsProject structureDeployment
AuthenticationWorkspaces and the admin area
Building with agentsEmails with Resend
TroubleshootingNextbase v5
The 8 starter kitsNext.js + Convex

Authentication

How Convex Auth works in the Next.js + Convex starter: providers, sessions, the admin app and role, and how functions are protected.

Convex Auth owns users and sessions in this kit. The same login is used by the web app, the admin app, server components and agent connections. For the concepts shared across all kits, read the v5 authentication guide first.

Where the configuration lives

FileWhat it does
apps/database/convex/auth.tsDeclares the providers and the convexAuth callbacks
apps/database/convex/auth.config.tsRegisters CONVEX_SITE_URL as the issuer domain with application id convex
apps/database/convex/http.tsMounts the Convex Auth HTTP routes and the MCP routes
apps/database/convex/lib/authz.tsrequireUser, requireWorkspaceRole, requirePlatformAdmin and their trusted *Id variants
apps/database/convex/lib/auth_redirect.tsAllows redirects only to the SITE_URL and ADMIN_SITE_URL origins
apps/database/convex/lib/user_profiles.tsCreates the userProfiles, userSettings and platformRoles rows for a new user
apps/database/convex/platform.tsThe internal grantAdmin mutation that promotes an account to application administrator
apps/web/src/proxy.tsWeb middleware built on convexAuthNextjsMiddleware
apps/admin/src/proxy.tsAdmin middleware: sends signed-out visitors of /app-admin to the admin /login
apps/web/src/app/convex-client-provider.tsxBrowser Convex client with auth

Provider credentials and signing keys are deployment environment variables. See environment variables for the list.

Providers

auth.ts registers these providers:

  • Password with verify set to the email provider and reset set to the password reset provider. Emails are trimmed and lower-cased; passwords must have at least 10 characters.
  • Email magic link (id: "email", valid for 15 minutes). The login and sign-up pages call signIn("password") and signIn("email").
  • Password reset (id: "password-reset", 15 minutes). The email links to /update-password?mode=reset&email=...&reset_code=... on SITE_URL.
  • GitHub and Google, only when both AUTH_GITHUB_ID and AUTH_GITHUB_SECRET (or AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET) exist on the deployment. The login and sign-up pages ship without provider buttons. Add a button that calls signIn("github") or signIn("google") when you configure a provider, and register the callback URL on the provider's side using your .convex.site origin.

After a user is created or updated, the afterUserCreatedOrUpdated callback calls ensureUserProfile. The redirect callback passes the target through authRedirect, which allows only the SITE_URL origin and, when set, the ADMIN_SITE_URL origin, and falls back to /dashboard on the web origin.

Email verification and the local inbox

Sign-up sends a verification code and link through the email provider. In production the provider posts to the Resend API with AUTH_RESEND_KEY and AUTH_EMAIL_FROM. When the backend has APP_ENV=local or APP_ENV=test, nothing is sent; the message is logged as [local-auth-email] with the code and URL, and convex dev prints it in the pnpm dev terminal.

The password reset provider has no local branch and always needs SITE_URL, AUTH_RESEND_KEY and AUTH_EMAIL_FROM. Set them on your local deployment with pnpm exec convex env set if you want to test resets locally. See emails with Resend for the production setup.

Pages and sessions

Web app (apps/web, port 3000):

PathPurpose
/sign-up, /loginCreate an account or sign in. Signed-in visitors are redirected to /dashboard, or to a pending /mcp/authorize?... request
/forgot-password, /update-passwordRequest and complete a password reset
/onboardingAccept terms, complete the profile, create the first workspace (convex/onboarding.ts)
/dashboardEntry point after login
/auth/impersonateReceives a single-use impersonation handoff from the admin app and starts a 15-minute support session
/logoutEnds the session

Admin app (apps/admin, port 3002):

PathPurpose
/loginPassword or magic-link sign-in for administrators. Signed-in visitors go to /app-admin, or to a pending /mcp/authorize?... request
/auth/callbackReturns magic-link and OAuth callbacks to /app-admin
/app-adminThe admin console. Anyone without the admin platform role gets the unauthorized page
/mcp/authorizeConsent page for the Admin MCP

apps/web/src/proxy.ts redirects unauthenticated requests to /login for /dashboard, /settings, /profile, /workspace, /project, /home, /user, /logout and /onboarding. It also adds a request id header and sets X-Frame-Options: DENY, a frame-ancestors 'none' policy and Referrer-Policy: no-referrer on /mcp/* pages. apps/admin/src/proxy.ts does the same for /app-admin. Both middlewares are coarse gates; pages and Convex functions still check access.

Server components read the session with convexAuthNextjsToken() and pass it to fetchQuery or fetchMutation. Client components use useQuery and useMutation from convex/react. Sessions are rows in the Convex Auth authSessions table. The admin app has its own origin-scoped session, so signing in on localhost:3002 does not sign you in on localhost:3000. MCP connections are bound to the session that approved them, so signing out of that session also ends those connections.

Legacy Supabase auth routes

/auth/callback, /auth/confirm and /confirm-delete-user/[token] in the web app still call the Supabase client, as do apps/web/src/middlewares/ (unused; proxy.ts is the real middleware) and the Supabase-era e2e helpers. Convex Auth does not use these routes. Remove or port them before shipping.

Becoming an application administrator

Application-wide administration is a row in the platformRoles table with role: "admin". ensureUserProfile gives every new user role: "user". Nothing in the sign-up flow promotes an account; the operator does it from the CLI.

Sign up normally in the web app, then run the internal mutation from the repository root against the selected deployment:

pnpm exec convex run platform:grantAdmin '{"email":"you@example.com"}'

platform:grantAdmin (apps/database/convex/platform.ts) looks the account up by normalised email, refuses unknown, disabled or deleted accounts, inserts or patches the platformRoles row, and returns { userId, changed }. It is idempotent and an internalMutation, so it is never callable from a client. For a production deployment add --prod: pnpm exec convex run --prod platform:grantAdmin '{"email":"..."}'. platform.test.ts covers the promote and idempotent path and the unknown and disabled account rejections.

Then sign in to the admin app at /login on the admin origin. The web app reads the role through api.users.viewer (isAppAdmin); the admin app's /app-admin layout calls serverGetLoggedInUserClaims() and unauthorized() for everyone else.

The admin app

Platform administration no longer lives in apps/web. The admin console is /app-admin in apps/admin, with users, workspaces, marketing content (blog, changelog, authors, tags), feedback moderation, the payment gateway, settings and /app-admin/connections for the Admin MCP. The demo admin is at https://nextjs-convex-admin.usenextbase.com. Being a workspace owner never grants application administration.

The two apps share the Convex deployment and every privileged operation is authorised in Convex, not in the browser. ADMIN_SITE_URL on the deployment tells the backend which second origin to trust for auth redirects and admin consent. The admin app's NEXT_PUBLIC_WEB_APP_URL and the web app's NEXT_PUBLIC_ADMIN_APP_URL let each app link to the other.

Impersonation. From the admin user list choose Impersonate in web app and enter a support reason. Convex requires an active administrator session, creates a one-use token, stores only its SHA-256 hash and writes the reason to the audit log. The handoff expires in 60 seconds and travels in the URL fragment to NEXT_PUBLIC_WEB_APP_URL + /auth/impersonate, where the web app exchanges it for a 15-minute target-user session. Expired or replayed links, demoted administrators and administrator targets are rejected; the admin session stays signed in on its own origin. End support session in the web app signs the temporary session out. The repository's docs/separate-admin-app.md has the full description.

What is protected and how

Convex has no row-level security; authorization is code in each function. The rules the kit follows:

  • Every public query or mutation starts with a check from convex/lib/authz.ts. requireUser resolves the Convex Auth user and rejects disabled or deleted profiles. requireWorkspaceRole(ctx, workspaceId, minRole) checks workspaceMembers against the role order readonly < member < admin < owner. requirePlatformAdmin checks platformRoles.
  • Functions never accept a userId from the client. The requireUserId, requireWorkspaceRoleId and requirePlatformAdminId variants exist for trusted internal callers such as MCP tools.
  • Shared domain helpers, for example createProjectForActor in convex/projects.ts, enforce the same rules for the website and for agents. Creating a project needs at least the member role.
  • Anything not called from the browser is an internalQuery or internalMutation. MCP tool handlers and platform:grantAdmin are internal functions.
  • Errors are thrown with appError(code, message) from convex/lib/errors.ts.
  • Backend tests in apps/database/convex/*.test.ts use convexTest with t.withIdentity({ subject: "<userId>|<sessionId>" }) and cover the unauthorized and wrong-role paths.

Server actions in both apps go through authActionClient and adminActionClient from each app's src/lib/safe-action.ts. Admin layouts use serverGetLoggedInUserClaims() and unauthorized(). UI visibility is a convenience, not the security boundary.

Environment variables

Every variable in the Next.js + Convex starter, split between the web env file, the admin env file and the Convex deployment.

Agent connections

The User MCP and Admin MCP of the Next.js + Convex starter: endpoints, tools, OAuth consent and how to add a tool.

On this page

Where the configuration livesProvidersEmail verification and the local inboxPages and sessionsBecoming an application administratorThe admin appWhat is protected and how