Shipkit
DocsFeaturesPricing
DocsFeaturesPricing

Command Palette

Search for a command to run...

Shipkit DocumentationBuild memory optimizationDevelopment GuideEnvironment VariablesError HandlingFile StructureMiddlewareRebranding Guide

Vercel Deployment Checks

Development GuideCLIDependency UpdatesDevToolsFeature FlagsAI PromptsSetup RequirementsSupply chaintRPCWeb Workers

FeaturesAIAnalytics IntegrationAuthenticationCMSDatabaseEmailHapticsPaymentsComponent RegistryStorageUIVisual BuilderWaitlist Feature

Quick StartDeploymentEnvironment VariablesFile StructureSetup Wizard

Caching & Rate LimitingContent ManagementError HandlingMarkdown and MDXMiddlewareProxy-Safe Server ActionsRebranding GuideWebhook Security Implementation GuideMulti-Zone ArchitectureVercel Deployment

IntegrationsPayload CMSDataFast Analytics IntegrationGoogle Analytics IntegrationGoogle Tag Manager IntegrationPostHogStatSig IntegrationUmami Analytics IntegrationAuth.js IntegrationBetter AuthClerkStack Auth IntegrationSupabase Authentication IntegrationBuilder.io IntegrationPayload CMS IntegrationAWS S3 IntegrationResend Email IntegrationUpstash Redis IntegrationVercel Blob IntegrationLemon SqueezyPolarStripe

API RoutesComponentsAPI Route SnippetsAuthentication SnippetsComponent SnippetsForm Handling SnippetsSnippets Introduction

Authentication

Shipkit uses NextAuth.js v5 with a database-backed session strategy. Authentication is controlled by the NEXT_PUBLIC_FEATURE_AUTH_ENABLED master flag. Individual providers are toggled independently.

Sign In

Supported Providers

OAuth Providers

ProviderFlagRequired Env Vars
GitHubNEXT_PUBLIC_FEATURE_AUTH_GITHUB_ENABLEDAUTH_GITHUB_ID, AUTH_GITHUB_SECRET
GoogleNEXT_PUBLIC_FEATURE_AUTH_GOOGLE_ENABLEDAUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET
DiscordNEXT_PUBLIC_FEATURE_AUTH_DISCORD_ENABLEDAUTH_DISCORD_ID, AUTH_DISCORD_SECRET
TwitterNEXT_PUBLIC_FEATURE_AUTH_TWITTER_ENABLEDAUTH_TWITTER_ID, AUTH_TWITTER_SECRET
GitLabNEXT_PUBLIC_FEATURE_AUTH_GITLAB_ENABLEDAUTH_GITLAB_ID, AUTH_GITLAB_SECRET
BitbucketNEXT_PUBLIC_FEATURE_AUTH_BITBUCKET_ENABLEDAUTH_BITBUCKET_ID, AUTH_BITBUCKET_SECRET

All OAuth providers have allowDangerousEmailAccountLinking enabled, so multiple providers can link to the same email automatically.

Email (Magic Link)

Set NEXT_PUBLIC_FEATURE_AUTH_RESEND_ENABLED=true and provide RESEND_API_KEY. Users receive a magic link to sign in without a password.

Credentials (Email/Password)

Set NEXT_PUBLIC_FEATURE_AUTH_CREDENTIALS_ENABLED=true. Requires Payload CMS to be enabled (it manages user accounts and passwords). Passwords are hashed with scrypt.

Guest Access

Set NEXT_PUBLIC_FEATURE_AUTH_GUEST_ENABLED=true. Users enter a display name and get a JWT-only session (no database persistence). User IDs are formatted as guest_{timestamp}_{name}.

When only guest auth is enabled, the system switches to JWT-only strategy automatically.

Session Handling

  • Database sessions (default): Stored in PostgreSQL via the Drizzle adapter. Max age: 30 days, update interval: 24 hours.
  • JWT sessions: Used for guest mode or when the database is unavailable.

The session includes user.isAdmin (boolean) populated from the JWT callback.

Protecting Routes

Server Components

import { auth } from "@/server/lib/auth";

export default async function ProtectedPage() {
  const session = await auth({ protect: true });
  // Redirects to /sign-in if not authenticated
  return <div>Hello {session.user.name}</div>;
}

Admin Routes

Admin routes use a layout-level check in src/app/(app)/(admin)/layout.tsx:

import { auth } from "@/server/lib/auth";
import { isAdmin } from "@/server/services/admin-service";

export default async function AdminLayout({ children }) {
  const session = await auth();
  if (!session?.user?.email || !isAdmin({ email: session.user.email })) {
    redirect(routes.home);
  }
  return <>{children}</>;
}

Server Actions

"use server";
import { auth } from "@/server/lib/auth";

export async function myProtectedAction() {
  const session = await auth({ protect: true });
  // session.user is guaranteed to exist here
}

Client Components

"use client";
import { useIsAdmin } from "@/hooks/use-is-admin";

export function AdminButton() {
  const isAdmin = useIsAdmin();
  if (!isAdmin) return null;
  return <button>Admin Action</button>;
}

Admin Role System

Admin status is determined by checking (in order):

  1. Email config: ADMIN_EMAIL env var (comma-separated list) or ADMIN_DOMAINS (domain-based matching). Default admin: me@lacymorrow.com.
  2. Database role: User record with role = "admin" in the users table.
  3. RBAC permission: User has the system:admin permission.
  4. Payload CMS: User exists in the Payload CMS users collection.

If any check passes, the user is admin.

RBAC

The RBAC system (src/server/services/rbac.ts) provides granular permissions:

  • Resources: team, project, user, api-key, billing, settings
  • Actions: create, read, update, delete, manage
  • Context-aware: Permissions can be scoped to teams or projects

Check permissions in API routes:

const check = await RBACService.checkPermission(req, {
  resource: "team",
  action: "create",
  context: { teamIdParam: "teamId" },
});
// Returns 401/403 response if unauthorized

Auth Server Actions

Available in src/server/actions/auth.ts:

ActionPurpose
signInWithOAuthAction(providerId)Start OAuth flow
signInAction(input)Credentials sign-in
signUpWithCredentialsAction(formData)User registration
signOutAction()Sign out
forgotPasswordAction(email)Send password reset email
resetPasswordAction(token, password)Complete password reset

Auth Pages

RoutePurpose
/sign-inSign-in page
/sign-outSign-out page
/auth/errorAuth error page

Key Files

FilePurpose
src/server/auth.tsNextAuth exports (auth, signIn, signOut)
src/server/auth-js/auth.config.tsProvider config, callbacks, session strategy
src/server/auth-js/auth-providers.config.tsProvider definitions and feature flags
src/server/services/auth-service.tsAuth business logic
src/server/services/admin-service.tsAdmin role checking
src/server/services/rbac.tsPermission system
src/server/actions/auth.tsAuth server actions

Alternative Auth Libraries

Shipkit also supports Better Auth, Clerk, and Supabase Auth as alternatives. See the integration docs for setup.