Auth Guards
How to protect API routes and pages with authentication and authorization guards.
API guards
requireApiAuth()
Require authentication for an API route. Returns { session, userId } on success, or { error } (401 response) on failure.
File: lib/auth/api-guard.ts
import { requireApiAuth } from "@/lib/auth/api-guard";
export async function GET(request: NextRequest) {
const { userId, error } = await requireApiAuth();
if (error) return error;
// userId is available here
}requireApiAdmin()
Require admin role. Returns 401 if not authenticated, 403 if not admin.
import { requireApiAdmin } from "@/lib/auth/api-guard";
export async function GET(request: NextRequest) {
const { session, userId, error } = await requireApiAdmin();
if (error) return error;
// Only admins reach here
}Subscription guards
File: lib/auth/subscription-guard.ts
requireSubscription()
Gate an API route to users with an active subscription.
import { requireSubscription } from "@/lib/auth/subscription-guard";
export async function GET(request: NextRequest) {
const { authorized, response, userId } = await requireSubscription();
if (!authorized) return response;
// User has an active subscription
}requirePlan(planId)
Require a specific plan.
const { authorized, response, currentPlan } = await requirePlan("starter");
if (!authorized) return response;requireLimit(key, currentUsage)
Check a plan's limit against current usage. The limit value comes from billing.config.ts — use -1 for unlimited.
const { authorized, response } = await requireLimit("requestsPerDay", currentCount);
if (!authorized) return response;getSubscriptionStatus()
Non-blocking check — returns subscription info without blocking the request.
const { isAuthenticated, hasActiveSubscription, plan, status, userId } =
await getSubscriptionStatus();Page guards
File: lib/auth/utils.ts
getCurrentUser()
Get the current user on the server side. Returns null if not logged in.
import { getCurrentUser } from "@/lib/auth/utils";
export default async function Page() {
const user = await getCurrentUser();
if (!user) redirect("/auth/login");
// ...
}requireAuth()
Redirects to /auth/login if not authenticated. Returns the session.
import { requireAuth } from "@/lib/auth/utils";
export default async function ProtectedPage() {
const session = await requireAuth();
// session.user is guaranteed to exist
}requireAdmin()
Redirects to /auth/login if not authenticated, then redirects to / if not admin.
import { requireAdmin } from "@/lib/auth/utils";
export default async function AdminPage() {
const session = await requireAdmin();
// session.user.role === "admin"
}How route protection works
The primary layer of protection is proxy.ts (Next.js middleware). Every route is protected by default — only routes in the publicRoutes array (config/public-routes.config.ts) are accessible without authentication. Unauthenticated users are redirected to /auth/login.
The protected layout at app/(protected)/layout.tsx adds a second layer by fetching the user for the sidebar UI. API routes use requireApiAuth() and requireApiAdmin() as their own protection layer, returning error responses instead of redirecting.