Authentication
How authentication works in this template
This template uses Whop OAuth 2.1 + PKCE for authentication. Users sign in with their Whop account — no passwords to manage, no client secret required.
How It Works
- User clicks "Sign in" → redirected to Whop's authorize page
- User approves → redirected back with an authorization code
- The callback exchanges the code for user info via PKCE
- A JWT session cookie is created (7-day expiry)
Protecting Pages
The dashboard layout calls requireSession() which redirects unauthenticated users to /login:
// app/dashboard/layout.tsx
import { requireSession } from "@/lib/auth";
export default async function DashboardLayout({ children }) {
const session = await requireSession();
// session.userId, session.email, session.plan, session.isAdmin
return <>{children}</>;
}Session Helpers
getSession() — Optional auth check
Returns the session or null. Use in server components and API routes where auth is optional:
import { getSession } from "@/lib/auth";
const session = await getSession();
if (session) {
// User is logged in
}requireSession() — Required auth check
Returns the session or redirects to /login. Use in protected pages:
import { requireSession } from "@/lib/auth";
const session = await requireSession();
// Always authenticated herePlan Gating
The user's plan is always fresh from the database (kept up-to-date by Whop webhooks). Several helpers make plan gating easy:
requirePlan() — Gate entire pages
Redirects to /pricing if the user's plan is below the required level:
import { requirePlan } from "@/lib/auth";
// Only starter and pro users can access this page
const session = await requirePlan("starter");hasMinimumPlan() — Check plan level in API routes
Pure function for plan comparison without redirects:
import { getSession, hasMinimumPlan } from "@/lib/auth";
const session = await getSession();
if (!session || !hasMinimumPlan(session.plan, "starter")) {
return NextResponse.json({ error: "Upgrade required" }, { status: 403 });
}<PlanGate> — Conditional rendering in client components
Pass the plan from a server parent to gate UI elements:
import { PlanGate } from "@/components/plan-gate";
// In a server component
const session = await requireSession();
return (
<PlanGate plan={session.plan} minimum="starter" fallback={<UpgradeBanner />}>
<AdvancedFeature />
</PlanGate>
);Direct Whop API access check
For authoritative real-time access verification (e.g., before high-value operations):
import { hasWhopAccess } from "@/lib/whop";
const { hasAccess } = await hasWhopAccess(session.whopUserId, "prod_xxxxx");Plan hierarchy
The plan hierarchy is auto-derived from key order in PLAN_METADATA (lib/constants.ts). With the default tiers: pro > starter > free. All helpers respect this hierarchy — a pro user can access starter-gated features. If you add or remove tiers, the hierarchy updates automatically.
Admin Access
The first user to sign in becomes the admin. Check session.isAdmin for admin-only features:
const session = await requireSession();
if (session.isAdmin) {
// Show admin settings
}