SaaS Core/Documentation

Authentication Configuration

This guide covers the complete setup and customization of the Better Auth authentication system used in SaaS Core, including comprehensive OAuth provider configuration.

๐Ÿ” Authentication Overview

SaaS Core uses Better Auth as its authentication solution, providing:

  • Email and password authentication
  • Social login (Google, GitHub)
  • Magic link authentication
  • Session management
  • Multi-tenant organization support
  • Role-based access control

๐Ÿš€ Quick Setup

1. Environment Variables

Add these authentication-related variables to your .env.local file:

# ===========================================
# Authentication Configuration
# ===========================================
BETTER_AUTH_SECRET="your-super-secret-key-here-make-it-long-and-random"
BETTER_AUTH_URL="http://localhost:3000"

# ===========================================
# OAuth Configuration
# ===========================================
# Google OAuth
GOOGLE_CLIENT_ID="123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="GOCSPX-abcdefghijklmnopqrstuvwxyz123456"

# GitHub OAuth
GITHUB_CLIENT_ID="abcdef1234567890abcd"
GITHUB_CLIENT_SECRET="abcdef1234567890abcdef1234567890abcdef12"

# Email Service (Magic Links)
RESEND_API_KEY="your-resend-api-key"

2. Generate Authentication Secret

Generate a secure authentication secret:

# Using OpenSSL
openssl rand -base64 32

# Using Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

3. Generate Authentication Schema

npx @better-auth/cli generate

Copy the generated auth schema content to your Drizzle schema in src/db/schema.ts.

4. Verify Authentication Setup

Start your development server and test the authentication flow:

pnpm run dev

Visit http://localhost:3000/auth/signin to test the sign-in process.


๐Ÿ”ง Detailed Configuration

Better Auth Setup

The main authentication configuration is in src/lib/auth.ts:

import { stripe } from "@better-auth/stripe";
import { type BetterAuthOptions, betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { nextCookies } from "better-auth/next-js";
import { magicLink, openAPI, organization } from "better-auth/plugins";

import { db } from "@/db/drizzle";
import { schema } from "@/db/schema";

export const auth = betterAuth({
  trustedOrigins: ["https://*.zifyai.com", "http://localhost:3000"],
  database: drizzleAdapter(db, {
    provider: "pg",
    schema,
  }),
  user: {
    deleteUser: {
      enabled: true,
    },
    additionalFields: {
      stripeCustomerId: {
        type: "string",
        required: false,
      },
    },
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7 days
    updateAge: 60 * 60 * 24, // 1 day
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // Cache duration in seconds
    },
  },
  emailAndPassword: {
    enabled: true,
    minPasswordLength: 8,
    maxPasswordLength: 20,
    requireEmailVerification: true,
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
  plugins: [
    magicLink({
      sendMagicLink: async ({ email, token, url }) => {
        // Send magic link email
        await sendEmail({
          to: email,
          subject: "Sign in to SaaS Core",
          html: `
            <h2>Sign in to SaaS Core</h2>
            <p>Click the link below to sign in:</p>
            <a href="${url}">Sign In</a>
            <p>This link will expire in 15 minutes.</p>
          `,
        });
      },
    }),
    organization({
      enabled: true,
      defaultRole: "member",
      roles: ["owner", "admin", "member"],
    }),
    openAPI({
      enabled: process.env.NODE_ENV === "development",
    }),
  ],
});

Session Configuration

Customize session settings based on your security requirements:

session: {
  expiresIn: 60 * 60 * 24 * 7, // 7 days
  updateAge: 60 * 60 * 24, // Update session every 24 hours
  cookie: {
    name: "saas-core-session",
    secure: process.env.NODE_ENV === "production",
    sameSite: "lax",
    httpOnly: true,
  },
  cookieCache: {
    enabled: true,
    maxAge: 5 * 60, // 5 minutes cache
  },
}

Email Verification

Configure email verification settings:

emailAndPassword: {
  enabled: true,
  minPasswordLength: 8,
  maxPasswordLength: 20,
  requireEmailVerification: true,
  sendVerificationEmail: async ({ email, token, url }) => {
    await sendEmail({
      to: email,
      subject: "Verify your email address",
      html: `
        <h2>Verify your email address</h2>
        <p>Click the link below to verify your email:</p>
        <a href="${url}">Verify Email</a>
        <p>This link will expire in 24 hours.</p>
      `,
    });
  },
}

๐Ÿ”‘ OAuth Provider Setup

Google OAuth Configuration

1. Create Google Cloud Project

  1. Visit Google Cloud Console
  2. Click the project selector, then click "New Project"
  3. Enter project name (e.g., saas-core-pro)
  4. Select organization (optional)
  5. Click "Create"

2. Enable Required APIs

  1. In Google Cloud Console, navigate to "APIs & Services" > "Library"
  2. Search for "Google+ API" or "People API"
  3. Click "Google+ API", then click "Enable"
  4. Also enable "People API" (recommended)

3. Configure OAuth Consent Screen

  1. Navigate to "APIs & Services" > "OAuth consent screen"

  2. Select user type:

    • Internal: Only organization users (G Suite)
    • External: Any Google user
  3. Fill in application information:

    Application name: SaaS Core
    User support email: [email protected]
    Application logo: Upload your app icon (optional)
    Application homepage: https://yourdomain.com
    Application privacy policy link: https://yourdomain.com/privacy
    Application terms of service link: https://yourdomain.com/terms
    
  4. Add authorized domains:

    yourdomain.com
    localhost (development only)
    
  5. Configure scopes:

    • ../auth/userinfo.email
    • ../auth/userinfo.profile
    • openid

4. Create OAuth 2.0 Credentials

  1. Navigate to "APIs & Services" > "Credentials"

  2. Click "Create credentials" > "OAuth 2.0 Client IDs"

  3. Select application type: Web application

  4. Configure client information:

    Name: SaaS Core Web Client
    
    Authorized JavaScript origins:
    - http://localhost:3000 (development)
    - https://yourdomain.com (production)
    
    Authorized redirect URIs:
    - http://localhost:3000/api/auth/callback/google (development)
    - https://yourdomain.com/api/auth/callback/google (production)
    
  5. Click "Create"

  6. Copy the generated Client ID and Client Secret

5. Environment Variables

# Google OAuth Configuration
GOOGLE_CLIENT_ID="123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="GOCSPX-abcdefghijklmnopqrstuvwxyz123456"

GitHub OAuth Configuration

1. Create GitHub OAuth Application

  1. Log in to GitHub, visit Developer Settings
  2. Click "OAuth Apps" tab
  3. Click "New OAuth App" button
  4. Fill in application information:
    Application name: SaaS Core
    Homepage URL: https://yourdomain.com
    Application description: Modern SaaS template for global products
    Authorization callback URL: https://yourdomain.com/api/auth/callback/github
    

2. Development Environment Configuration

Create a separate OAuth application for development:

Application name: SaaS Core (Development)
Homepage URL: http://localhost:3000
Authorization callback URL: http://localhost:3000/api/auth/callback/github

3. Get Client Credentials

  1. After creating the application, you'll see "Client ID"
  2. Click "Generate a new client secret" to generate client secret
  3. Important: Copy the client secret immediately, it will only be shown once

4. Environment Variables

# GitHub OAuth Configuration
GITHUB_CLIENT_ID="abcdef1234567890abcd"
GITHUB_CLIENT_SECRET="abcdef1234567890abcdef1234567890abcdef12"

๐Ÿข Multi-Tenant Organization Setup

SaaS Core supports multi-tenant organizations with role-based access:

Organization Schema

// In your Drizzle schema
export const organizations = pgTable("organizations", {
  id: text("id").primaryKey(),
  name: text("name").notNull(),
  slug: text("slug").notNull().unique(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});

export const organizationMembers = pgTable("organization_members", {
  id: text("id").primaryKey(),
  userId: text("user_id").notNull(),
  organizationId: text("organization_id").notNull(),
  role: text("role").notNull().default("member"), // owner, admin, member
  joinedAt: timestamp("joined_at").defaultNow().notNull(),
});

Organization Management

// Create organization
const org = await auth.api.createOrganization({
  name: "My Organization",
  slug: "my-org",
});

// Invite members
await auth.api.inviteToOrganization({
  organizationId: org.id,
  email: "[email protected]",
  role: "member",
});

๐Ÿ”’ Security Configuration

Rate Limiting

Implement rate limiting for authentication endpoints:

// Example with Redis rate limiting
import { Redis } from "ioredis";

const redis = new Redis(process.env.REDIS_URL);

export async function rateLimit(key: string, limit: number, window: number) {
  const current = await redis.incr(key);
  if (current === 1) {
    await redis.expire(key, window);
  }
  return current <= limit;
}

Password Policies

Customize password requirements:

emailAndPassword: {
  enabled: true,
  minPasswordLength: 8,
  maxPasswordLength: 128,
  requireEmailVerification: true,
  passwordPolicy: {
    requireUppercase: true,
    requireLowercase: true,
    requireNumbers: true,
    requireSpecialCharacters: true,
  },
}

Security Headers

Add security headers to your Next.js configuration:

// next.config.js
const securityHeaders = [
  {
    key: 'X-Frame-Options',
    value: 'DENY'
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff'
  },
  {
    key: 'Referrer-Policy',
    value: 'origin-when-cross-origin'
  }
];

๐Ÿงช Testing Authentication

Test Authentication Flow

Create a test script to verify authentication setup:

# Create test script
cat > scripts/test-auth.js << 'EOF'
const { auth } = require('../src/lib/auth');

async function testAuthentication() {
  console.log('๐Ÿ” Testing authentication setup...');

  try {
    // Test configuration
    console.log('โœ… Better Auth configuration loaded');

    // Test environment variables
    const requiredVars = [
      'BETTER_AUTH_SECRET',
      'BETTER_AUTH_URL',
      'GOOGLE_CLIENT_ID',
      'GITHUB_CLIENT_ID'
    ];

    requiredVars.forEach(varName => {
      if (process.env[varName]) {
        console.log(`โœ… ${varName}: Configured`);
      } else {
        console.log(`โŒ ${varName}: Missing`);
      }
    });

    console.log('โœ… Authentication test completed');

  } catch (error) {
    console.error('โŒ Authentication test failed:', error.message);
  }
}

testAuthentication();
EOF

# Run test
node scripts/test-auth.js

Manual Testing Checklist

  • [ ] Sign up with email and password
  • [ ] Verify email address
  • [ ] Sign in with email and password
  • [ ] Sign in with Google OAuth
  • [ ] Sign in with GitHub OAuth
  • [ ] Use magic link authentication
  • [ ] Test password reset
  • [ ] Verify session persistence
  • [ ] Test sign out

Testing OAuth Login

  1. Start development server: pnpm run dev
  2. Visit login page: http://localhost:3000/auth/signin
  3. Click "Continue with Google" or "Continue with GitHub"
  4. Complete OAuth authorization flow
  5. Verify user information is correctly saved

๐Ÿ”’ Security Best Practices

1. Client Secret Security

  • Never expose client secrets in client-side code
  • Use environment variables to store sensitive information
  • Regularly rotate client secrets
  • Use different OAuth applications for different environments

2. Redirect URI Validation

  • Only add necessary redirect URIs
  • Use HTTPS (production environment)
  • Avoid using wildcard redirect URIs

3. Scope Minimization

Only request application-required permissions:

socialProviders: {
  google: {
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    scope: 'openid email profile'  // Minimum permissions
  }
}

4. Rate Limiting

Implement rate limiting for authentication endpoints:

// Example with Redis rate limiting
import { Redis } from "ioredis";

const redis = new Redis(process.env.REDIS_URL);

export async function rateLimit(key: string, limit: number, window: number) {
  const current = await redis.incr(key);
  if (current === 1) {
    await redis.expire(key, window);
  }
  return current <= limit;
}

5. Password Policies

Customize password requirements:

emailAndPassword: {
  enabled: true,
  minPasswordLength: 8,
  maxPasswordLength: 128,
  requireEmailVerification: true,
  passwordPolicy: {
    requireUppercase: true,
    requireLowercase: true,
    requireNumbers: true,
    requireSpecialCharacters: true,
  },
}

6. Security Headers

Add security headers to your Next.js configuration:

// next.config.js
const securityHeaders = [
  {
    key: 'X-Frame-Options',
    value: 'DENY'
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff'
  },
  {
    key: 'Referrer-Policy',
    value: 'origin-when-cross-origin'
  }
];

๐ŸŒ Multi-Environment Configuration

Development Environment

BETTER_AUTH_URL="http://localhost:3000"
GOOGLE_CLIENT_ID="dev-google-client-id"
GITHUB_CLIENT_ID="dev-github-client-id"

Staging Environment

BETTER_AUTH_URL="https://staging.yourdomain.com"
GOOGLE_CLIENT_ID="staging-google-client-id"
GITHUB_CLIENT_ID="staging-github-client-id"

Production Environment

BETTER_AUTH_URL="https://yourdomain.com"
GOOGLE_CLIENT_ID="prod-google-client-id"
GITHUB_CLIENT_ID="prod-github-client-id"

๐Ÿšจ Troubleshooting

Debug Mode

Enable debug logging for authentication issues:

export const auth = betterAuth({
  // ... other config
  debug: process.env.NODE_ENV === "development",
});

Local Development HTTPS Issues

Problem: Some OAuth providers require HTTPS Solutions:

  • Use ngrok or similar tools to create HTTPS tunnels
  • Configure local HTTPS development environment
  • Use OAuth provider's developer mode when available

Getting Help

If you encounter authentication issues:

  1. Check the browser console for client-side errors
  2. Check server logs for backend errors
  3. Verify all environment variables are set correctly
  4. Test with different authentication methods
  5. Consult the Better Auth documentation

๐Ÿ“Š Monitoring and Analytics

OAuth Usage Statistics

Track OAuth provider usage in your database:

-- View OAuth provider usage statistics
SELECT
  provider,
  COUNT(*) as user_count,
  COUNT(*) * 100.0 / (SELECT COUNT(*) FROM accounts) as percentage
FROM accounts
GROUP BY provider;

Login Success Rate Monitoring

// Record authentication attempts
await db.insert(loginAttempts).values({
  provider: 'google',
  success: true,
  timestamp: new Date(),
  userAgent: req.headers['user-agent']
})

๐ŸŽฏ Next Steps

After setting up authentication, consider:

Your authentication system is now ready to secure your SaaS Core application!