Security

Authentication

Complete guide to authentication in NotebookLLM using Supabase Auth with OAuth and session management.

Overview

NotebookLLM uses Supabase Auth for user authentication. The system supports email/password authentication and OAuth providers (Google, GitHub). JWT tokens are used for API authorization.

Authentication Flow

How It Works

  1. User logs in via Supabase Auth (email/password or OAuth)
  2. Supabase returns JWT access and refresh tokens
  3. Frontend stores tokens via Supabase client
  4. API client includes JWT in Authorization header
  5. Backend validates JWT and extracts user ID
  6. Session refreshes automatically before expiration

Supabase Client Setup

Browser Client

Used in client components for authentication and data fetching.

// lib/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr"

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
  )
}

Server Client

Used in server components and API routes for authenticated requests.

// lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr"
import { cookies } from "next/headers"

export async function createClient() {
  const cookieStore = await cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            )
          } catch {
            // Called from Server Component
          }
        },
      },
    }
  )
}

Session Management

Automatic Session Refresh

Sessions are automatically refreshed every 50 minutes to prevent expiration.

// lib/auth/session-refresh.ts
import { createClient } from "@/lib/supabase/client"

let refreshInterval: ReturnType<typeof setInterval> | null = null

export function startSessionRefresh(): void {
  if (refreshInterval) return

  const REFRESH_INTERVAL_MS = 50 * 60 * 1000 // 50 minutes

  refreshInterval = setInterval(async () => {
    try {
      const supabase = createClient()
      const { error } = await supabase.auth.refreshSession()
      if (error) {
        console.warn("Session refresh failed:", error.message)
      }
    } catch {
      // Silently fail — user will be redirected on next action
    }
  }, REFRESH_INTERVAL_MS)
}

export function stopSessionRefresh(): void {
  if (refreshInterval) {
    clearInterval(refreshInterval)
    refreshInterval = null
  }
}

Using Session in Components

"use client"

import { createClient } from "@/lib/supabase/client"
import { useEffect, useState } from "react"

export function useSession() {
  const supabase = createClient()
  const [session, setSession] = useState<Session | null>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // Get initial session
    supabase.auth.getSession().then(({ data: { session } }) => {
      setSession(session)
      setLoading(false)
    })

    // Listen for changes
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        setSession(session)
        setLoading(false)
      }
    )

    return () => subscription.unsubscribe()
  }, [])

  return { session, loading }
}

Auth Event Logging

Comprehensive logging system for authentication events to monitor security and debug issues.

Event Types

EventDescription
LOGIN_SUCCESSUser logged in successfully
LOGIN_FAILURELogin attempt failed
LOGOUTUser logged out
OAUTH_COMPLETEDOAuth flow completed
SESSION_REFRESHSession refreshed
TOKEN_EXPIREDJWT token expired
UNAUTHORIZED_ACCESS401 response received

Using the Logger

import { authLogger } from "@/lib/auth-logger"

// Log successful login
authLogger.logLoginSuccess(userId, email)

// Log failed login
authLogger.logLoginFailure(email, errorMessage)

// Log OAuth flow
authLogger.logOAuthStarted("google", redirectUrl)
authLogger.logOAuthCompleted(userId, "google")

// Log unauthorized access (auto-handled by API client)
authLogger.logUnauthorizedAccess("/api/v1/documents", 401)

API Authorization

JWT in Requests

The API client automatically includes the JWT token in all requests.

// lib/api/client.ts
async function getAuthHeaders(): Promise<HeadersInit> {
  const supabase = createSupabaseClient()
  const {
    data: { session },
  } = await supabase.auth.getSession()

  if (!session?.access_token) {
    throw new ApiError(401, "Not authenticated")
  }

  return {
    Authorization: `Bearer ${session.access_token}`,
    "Content-Type": "application/json",
  }
}

Error Handling

The API client handles 401/403 errors by redirecting to login.

  • 401 - Unauthorized, redirect to login
  • 403 - Forbidden, redirect to login (except task endpoints)
  • 429 - Rate limited, auto-retry with backoff

OAuth Providers

Configure OAuth providers in the Supabase dashboard and backend environment.

ProviderBackend Variable
GoogleGOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
GitHubConfigured in Supabase
Email/PasswordBuilt-in with Supabase Auth

Protected Routes

Middleware Protection

// middleware.ts
import { createServerClient } from "@supabase/ssr"
import { NextResponse, type NextRequest } from "next/server"

export async function middleware(request: NextRequest) {
  let supabaseResponse = NextResponse.next({
    request,
  })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll()
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) =>
            request.cookies.set(name, value)
          )
          supabaseResponse = NextResponse.next({
            request,
          })
          cookiesToSet.forEach(({ name, value, options }) =>
            supabaseResponse.cookies.set(name, value, options)
          )
        },
      },
    }
  )

  // Check auth
  const { data: { session } } = await supabase.auth.getSession()

  if (!session && !request.nextUrl.pathname.startsWith("/auth")) {
    return NextResponse.redirect(new URL("/auth/login", request.url))
  }

  return supabaseResponse
}

Security Best Practices

Token Security

  • Tokens stored securely in httpOnly cookies
  • Auto-refresh before expiration
  • Clear on logout

API Security

  • JWT validation on all endpoints
  • Rate limiting enabled
  • CORS restricted to frontend

OAuth Security

  • State parameter for CSRF protection
  • Secure redirect URIs
  • Token rotation enabled

Monitoring

  • Auth events logged
  • Failed attempts tracked
  • Session anomalies detected