Shipkit
DocsFeaturesPricing
DocsFeaturesPricing

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.