Understand the secure Google OAuth integration and how user profiles are managed within AI Docs.
AI Docs provides a robust and secure authentication and user management system, enabling you to seamlessly sign in, manage your access, and control your personal data on the platform. This system is fundamental for personalizing your user experience, securing project data, and integrating with external services like GitHub for automated documentation generation.
AI Docs leverages better-auth, a flexible authentication library, integrated with Next.js API routes to handle user authentication flows. This setup simplifies the complexities of OAuth and session management, allowing you to focus on core application features.
The primary authentication entry point is handled by a dynamic Next.js API route:
// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth);
This route captures all authentication-related requests (e.g., /api/auth/signin/github, /api/auth/callback/github, /api/auth/signout) and processes them using the auth configuration.
The general authentication flow involves you initiating a sign-in, being redirected to a social provider, the provider authenticating you and redirecting back to AI Docs, and finally, AI Docs establishing a user session.
AI Docs supports signing in via popular social providers, with GitHub being the primary method due to its necessity for repository access.
GitHub is the recommended and primary authentication method for AI Docs. This is because AI Docs needs access to your GitHub repositories to automatically generate documentation. When you sign in with GitHub, AI Docs requests the repo scope, which grants it permission to read your repositories.
Signing in with GitHub is essential for AI Docs to access your private repositories and generate documentation from them. Without GitHub integration, the core functionality of AI Docs is limited to public repositories or manual content creation. For more details on repository access, refer to GitHub Integration.
You can also sign in using your Google account. While Google authentication provides a convenient way to access the AI Docs platform, it does not grant AI Docs access to your GitHub repositories. If you sign in with Google, you will still need to connect your GitHub account separately within your User Settings to utilize the full documentation generation capabilities. For detailed steps on configuring Google OAuth, refer to Environment Configuration.
The client-side authentication experience is handled using the better-auth/react client, which provides convenient functions for initiating sign-in and sign-out actions.
The signIn function is used to initiate the authentication flow with a specified social provider. This is typically triggered from the login page.
// app/login/page.tsx (simplified)
"use client";
import { signIn } from "@/lib/auth-client";
import { Button } from "@/components/ui/button";
import { Github, Loader2 } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
export default function LoginPage() {
const [loadingProvider, setLoadingProvider] = useState<string | null>(null);
const handleSignIn = async (provider: "google" | "github") => {
setLoadingProvider(provider);
try {
await signIn.social({
provider,
callbackURL: "/dashboard", // Redirect to dashboard after successful sign-in
});
} catch {
setLoadingProvider(null);
toast.error("Failed to sign in");
}
};
const isLoading = loadingProvider !== null;
return (
<div className="...">
{/* ... UI elements ... */}
handleSignIn("github")}
disabled={isLoading}
size="lg"
className="w-full font-mono"
>
{loadingProvider === "github" ? (
) : (
)}
{loadingProvider === "github"
? "Signing in..."
: "Continue with GitHub"}
{/* ... Divider ... */}
handleSignIn("google")}
disabled={isLoading}
variant="secondary"
size="lg"
className="w-full font-mono"
>
{/* ... Google Icon ... */}
{loadingProvider === "google"
? "Signing in..."
: "Continue with Google"}
</div>
);
}
The signOut function allows you to end your session. After a successful sign-out, you are typically redirected to the homepage or login page. This is commonly found in user dropdown menus or profile settings.
// components/nav-user.tsx (simplified)
"use client"
import { LogOut } from "lucide-react"
import { signOut } from "@/lib/auth-client"
import { DropdownMenuItem } from "@/components/ui/dropdown-menu"
export function NavUser({ user }: { user: { name: string; email: string; image?: string | null; }; }) {
const handleSignOut = async () => {
await signOut({
fetchOptions: {
onSuccess: () => {
// Hard reload to clear all cached state (Next.js router cache, React state, etc.)
window.location.href = "/";
},
},
});
}
if (!user) {
return null
}
return (
// ... other dropdown menu items ...
Log out
)
}
AI Docs provides utilities to access user session information on the server-side, enabling authenticated data fetching and API route protection.
The getUser utility function retrieves the currently authenticated user's details from the session. This is commonly used in server components or API routes to personalize content or authorize actions.
// lib/session.ts (simplified)
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { cache } from "react";
export const getSession = cache(async () => {
const session = await auth.api.getSession({
headers: await headers(),
});
return session;
});
export const getUser = cache(async () => {
const session = await getSession();
return session?.user;
});
To ensure that only authenticated users can access specific API endpoints, AI Docs provides a withAuth higher-order function. This wrapper automatically checks for a valid user session and returns an "Unauthorized" response if no user is found.
// lib/api/with-auth.ts (simplified)
import { NextResponse, NextRequest } from "next/server";
import { getUser } from "@/lib/session";
export type AuthUser = NonNullable<Awaited<ReturnType<typeof getUser>>>;
export function withAuth>(
handler: (
req: NextRequest,
user: AuthUser,
context: RouteContext
) => Promise
) {
return async (
req: NextRequest,
context: RouteContext
): Promise => {
const user = await getUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
try {
return await handler(req, user, context);
} catch (error: any) {
console.error(`API error [${req.method} ${req.url}]:`, error);
return NextResponse.json(
{ error: error.message || "Internal server error" },
{ status: 500 }
);
}
};
}
The Profile page (/profile) provides a comprehensive overview of your account, including personal details, connected social accounts, and key statistics about your documentation projects.
// app/profile/page.tsx (simplified)
import { getUser } from "@/lib/session";
import { db } from "@/lib/db";
import { projects, account, generatedDocs } from "@/lib/db/schema";
import { eq, desc, sql } from "drizzle-orm";
import { redirect } from "next/navigation";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { Github, Settings, Calendar } from "lucide-react";
export default async function ProfilePage() {
const user = await getUser();
if (!user) redirect("/login");
// Fetch connected OAuth providers
const userAccounts = await db
.select({ providerId: account.providerId })
.from(account)
.where(eq(account.userId, user.id));
const providers = userAccounts.map((a) => a.providerId);
const githubConnected = providers.includes("github");
const googleConnected = providers.includes("google");
const primaryProvider = providers[0] ?? null;
// Fetch project statistics
const allProjects = await db
.select({
id: projects.id,
repoName: projects.repoName,
repoOwner: projects.repoOwner,
status: projects.status,
updatedAt: projects.updatedAt,
})
.from(projects)
.where(eq(projects.userId, user.id))
.orderBy(desc(projects.updatedAt));
const projectCount = allProjects.length;
const readyCount = allProjects.filter((p) => p.status === "ready").length;
const recentProjects = allProjects.slice(0, 5);
// Calculate total documentation pages
let totalDocs = 0;
if (allProjects.length > 0) {
const projectIds = allProjects.map((p) => p.id);
const docsCount = await db
.select({ count: sql<number>`count(*)` })
.from(generatedDocs)
.where(
sql`${generatedDocs.projectId} IN (${sql.join(
projectIds.map((id) => sql`${id}`),
sql`, `
)})`
);
totalDocs = Number(docsCount[0]?.count ?? 0);
}
return (
<div className="...">
{/* ... HeroHeaderServer ... */}
<main className="...">
<div className="...">
{/* ... Profile header with avatar, name, email, join date, primary provider ... */}
{/* ... Edit button linking to /dashboard/settings ... */}
{/* Stats */}
<div className="grid grid-cols-3 gap-3 mb-4">
{/* Displays project count, total doc pages, and ready projects count */}
</div>
{/* Connected Accounts */}
<div className="rounded-2xl border border-white/8 bg-white/3 mb-4">
<div className="p-5">
<p className="font-mono text-[10px] tracking-widest text-white/30 uppercase mb-5">
Connected Accounts
</p>
<div className="flex flex-col gap-2">
{/* Displays GitHub and Google connection status */}
</div>
</div>
</div>
{/* Recent Projects */}
{/* ... List of recent projects or empty state ... */}
</div>
</main>
</div>
);
}
This page dynamically fetches and displays:
You can also access a user dropdown menu from the navigation bar (handled by components/nav-user.tsx) which shows your name, email, subscription status, and provides quick links to Project Settings and Customization and the option to log out.
The authentication system relies on several environment variables for proper functioning, especially for connecting to social OAuth providers. These variables must be set up as described in Environment Configuration.
Key environment variables include:
GOOGLE_CLIENT_ID: Client ID for Google OAuth.GOOGLE_CLIENT_SECRET: Client Secret for Google OAuth.GITHUB_CLIENT_ID: Client ID for GitHub OAuth.GITHUB_CLIENT_SECRET: Client Secret for GitHub OAuth.BETTER_AUTH_SECRET: A strong, random secret for session encryption.BETTER_AUTH_URL: The base URL of your AI Docs instance (e.g., https://www.docup.dev or http://localhost:3000).RESEND_API_KEY: API key for sending transactional emails, such as welcome emails.The lib/auth.ts file consolidates these configurations:
// lib/auth.ts (simplified)
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { Resend } from "resend";
import { db } from "./db";
import * as schema from "./db/schema";
import { getMainAppDomains } from "./config";
const resend = new Resend(process.env.RESEND_API_KEY);
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema: {
user: schema.user,
session: schema.session,
account: schema.account,
verification: schema.verification,
},
}),
emailAndPassword: {
enabled: false, // Email/password authentication is disabled
},
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!,
scope: ["repo"], // Requesting 'repo' scope for GitHub access
},
},
session: {
cookieCache: {
enabled: true,
maxAge: 5 * 60, // Session cookie cached for 5 minutes
},
},
trustedOrigins: [
// ... dynamically generated trusted origins ...
],
secret: process.env.BETTER_AUTH_SECRET!,
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
databaseHooks: {
user: {
create: {
after: async (user) => {
// Sends a welcome email to new users after account creation
const name = user.name || user.email.split("@")[0];
await resend.emails.send({
from: "Docup <noreply@docup.dev>",
replyTo: "mayurgadakh12@gmail.com",
to: user.email,
subject: "Welcome to Docup 👋",
html: getWelcomeEmailHtml(name), // HTML content for the welcome email
});
},
},
},
},
});
This configuration defines the database adapter, enables social providers (GitHub and Google) with their respective client IDs and secrets, and specifies the repo scope for GitHub to allow repository access. It also sets up session management and includes a databaseHook to send a welcome email to new users upon account creation.
clientSecret and BETTER_AUTH_SECRET secure and never expose them in client-side code. Use environment variables for all sensitive credentials. Refer to Environment Configuration for detailed setup.repo scope. AI Docs requests this scope to perform its core function of reading your repositories for documentation generation. Granting this scope allows AI Docs to access both public and private repositories you own or have access to. For more information, see GitHub Integration.better-auth library handles session cookies securely. Ensure your BETTER_AUTH_SECRET is strong and unique to prevent session hijacking.LoginPage and NavUser components.