Documentation

Authentication

Complete guide to setting up authentication with Auth.js v5, GitHub OAuth, and Google OAuth.

Overview

ShipSecure uses Auth.js v5 (formerly NextAuth.js) for authentication. It comes pre-configured with GitHub and Google OAuth providers, ready to use once you add your credentials.

Quick Start

  1. Generate AUTH_SECRET
  2. Configure OAuth Providers (GitHub and/or Google)
  3. Set Environment Variables
  4. Test Authentication

Step 1: Generate AUTH_SECRET

The AUTH_SECRET is used to encrypt session tokens. Generate one using:

npx auth secret

Or manually:

openssl rand -base64 32

Copy the generated secret to your .env file:

AUTH_SECRET=your-generated-secret-here

Step 2: Configure GitHub OAuth

Create a GitHub OAuth App

  1. Go to GitHub Developer Settings
  2. Click "New OAuth App"
  3. Fill in the details:
    • Application name: Your app name (e.g., "My SaaS")
    • Homepage URL: http://localhost:3000 (for development)
    • Authorization callback URL: http://localhost:3000/api/auth/callback/github
  4. Click "Register application"
  5. Copy the Client ID and generate a Client Secret

Add to Environment Variables

Add the credentials to your .env file:

GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret

Production Setup

Important: After deploying to production, update the callback URL in your GitHub OAuth App settings:

  1. Go to GitHub Developer Settings
  2. Click on your OAuth App
  3. Update Authorization callback URL to:
    https://your-domain.com/api/auth/callback/github
    
    Replace your-domain.com with your actual production domain (e.g., myapp.vercel.app or myapp.com)

Note: You can add multiple callback URLs (one for development, one for production) by clicking "Add another callback URL".

Step 3: Configure Google OAuth

Create a Google OAuth Client

  1. Go to Google Cloud Console

  2. Create a new project or select an existing one

  3. Navigate to APIs & Services > Credentials

  4. Click "Create Credentials" > "OAuth client ID"

  5. Configure the consent screen if prompted:

    • Choose "External" (unless you have a Google Workspace)
    • Fill in app name, user support email, and developer contact
  6. Create OAuth client:

    • Application type: Web application
    • Name: Your app name
    • Authorized JavaScript origins:
      • http://localhost:3000 (development)
      • https://your-domain.com (production - add after deployment)
    • Authorized redirect URIs:
      • http://localhost:3000/api/auth/callback/google (development)
      • https://your-domain.com/api/auth/callback/google (production - add after deployment)

    Important: Replace your-domain.com with your actual production domain. You can add multiple URIs by clicking the "+ Add URI" button.

  7. Click "Create"

  8. Copy the Client ID and Client Secret

Add to Environment Variables

Add the credentials to your .env file:

AUTH_GOOGLE_ID=your_google_client_id
AUTH_GOOGLE_SECRET=your_google_client_secret

Step 4: Complete Environment Setup

Your .env.local file should now include all authentication variables:

# Authentication - Replace all placeholder values with your actual credentials
AUTH_SECRET=your-generated-secret-here
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
AUTH_GOOGLE_ID=your_google_client_id
AUTH_GOOGLE_SECRET=your_google_client_secret

# Database (required) - Replace with your PostgreSQL connection string
DATABASE_URL=your_postgresql_connection_string

Checklist:

  • AUTH_SECRET generated and added
  • GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET from GitHub OAuth App
  • AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET from Google Cloud Console (if using Google)
  • DATABASE_URL configured (required for sessions to work)

Step 5: Test Authentication

  1. Start your development server (if not already running):
npm run dev
  1. Navigate to the sign-in page:

    http://localhost:3000/api/auth/signin
    
  2. Click on "Sign in with GitHub" or "Sign in with Google"

  3. Complete the OAuth flow:

    • You'll be redirected to GitHub/Google
    • Authorize the app
    • You'll be redirected back to your app
  4. Success! You should now be logged in. Check the top-right corner for your user menu.

Troubleshooting: If sign-in fails, check:

  • OAuth credentials are correct in .env.local
  • Callback URLs match exactly (including http vs https)
  • Database is initialized (npx prisma db push)

How It Works

Auth.js v5 Configuration

The authentication is configured in src/lib/auth.ts:

export const { handlers, signIn, signOut, auth } = NextAuth({
  adapter: PrismaAdapter(db),
  providers: [
    GitHub({
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,
    }),
    Google({
      clientId: process.env.AUTH_GOOGLE_ID,
      clientSecret: process.env.AUTH_GOOGLE_SECRET,
    }),
  ],
  // ... other config
});

Using Authentication in Your Code

Server Components

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

export default async function Page() {
  // Get the current session (works in Server Components)
  const session = await auth();

  // Check if user is authenticated
  if (!session) {
    return <div>Please sign in to view this page</div>;
  }

  // User is authenticated - access user data
  return <div>Hello {session.user?.name}!</div>;
}

When to use: Server Components are the default in Next.js App Router. Use this pattern for pages that need authentication.

Client Components

"use client";

import { useSession } from "next-auth/react";

export default function ClientPage() {
  // Get session in Client Component (requires "use client" directive)
  const { data: session, status } = useSession();

  // Handle loading state
  if (status === "loading") return <div>Loading...</div>;

  // Handle unauthenticated state
  if (!session) return <div>Please sign in</div>;

  // User is authenticated
  return <div>Hello {session.user?.name}!</div>;
}

When to use: Use this pattern in Client Components (components with interactivity, hooks, or browser APIs). Requires wrapping your app with SessionProvider (already configured in ShipSecure).

API Routes

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

export async function GET() {
  // Get session in API route
  const session = await auth();

  // Protect the route - return 401 if not authenticated
  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  // User is authenticated - return protected data
  return NextResponse.json({ user: session.user });
}

When to use: Protect API endpoints that require authentication. Place this file in app/api/your-endpoint/route.ts.

Server Actions

"use server";

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

export async function myServerAction() {
  // Get session in Server Action
  const session = await auth();

  // Protect the action - throw error if not authenticated
  if (!session) {
    throw new Error("Unauthorized");
  }

  // User is authenticated - perform protected operation
  // Example: Update user data, create records, etc.
  return { success: true, userId: session.user.id };
}

When to use: Protect Server Actions that modify data or perform sensitive operations. Call these from Client Components using form actions or button onClick handlers.

Security Features

ShipSecure's authentication includes:

  • CSRF Protection: Built into Auth.js v5
  • Secure Sessions: HTTP-only cookies
  • Database Sessions: Stored securely in PostgreSQL
  • Email Account Linking: Enabled for GitHub (allows linking accounts with same email)

Troubleshooting

"Invalid credentials" error

Check these:

  1. OAuth credentials are correct:

    • Verify GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET in .env.local
    • Verify AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET (if using Google)
    • Make sure there are no extra spaces or quotes around values
  2. Callback URLs match exactly:

    • Development: http://localhost:3000/api/auth/callback/github (note: http, not https)
    • Production: https://your-domain.com/api/auth/callback/github (note: https)
    • Check OAuth provider settings match your app URLs exactly
  3. AUTH_SECRET is set:

    • Run npx auth secret to generate a new one if needed
    • Make sure it's in .env.local (not .env.example)

Redirect loop

This happens when OAuth can't complete the flow. Check:

  1. Callback URLs in OAuth provider settings:

    • GitHub: Settings → Developer settings → OAuth Apps → Your app → Authorization callback URL
    • Google: Cloud Console → APIs & Services → Credentials → Your OAuth client → Authorized redirect URIs
    • Must match exactly: http://localhost:3000/api/auth/callback/github (development) or https://your-domain.com/api/auth/callback/github (production)
  2. AUTH_SECRET is consistent:

    • Same AUTH_SECRET must be used across all environments
    • If you changed it, clear browser cookies and try again
    • Verify it's set in .env.local (not just .env.example)
  3. Database is initialized:

    • Run npx prisma db push if you haven't already
    • Sessions are stored in database - if tables don't exist, auth will fail

Session not persisting

Sessions are stored in the database. Check:

  1. Database is configured:

    # Verify DATABASE_URL is set in .env.local
    echo $DATABASE_URL
    
  2. Prisma migrations have been run:

    # Initialize database schema
    npx prisma db push
    # Or for migrations:
    npx prisma migrate dev --name init
    
  3. Database tables exist:

    # Check if tables were created
    npx prisma studio
    # Should see: User, Account, Session tables
    
  4. Session strategy is correct:

    • ShipSecure uses "database" strategy by default (configured in src/lib/auth.ts)
    • This stores sessions in PostgreSQL, not cookies
    • If sessions aren't persisting, database connection is likely the issue

Next Steps