Chapter 5: Route Protection & Full-Stack Security: Middleware Configuration & Server Component Permission Control
In the first four chapters, we completed all foundational work. User data is securely stored in the Supabase database, and we can always track their session status.
Now, we finally arrive at implementing the core value of a SaaS platform: "Paywall & Route Protection."
If your system lacks proper route protection, users can simply guess URLs (e.g., manually entering https://vibe-tutor.com/courses/premium-video-1) to freeload your premium content.
In the Next.js App Router architecture, we have what the industry calls a "dual absolute defense" mechanism: Edge Middleware interception and Server Component database locking.
This chapter will guide you in weaving this protective net so tightly that not a single drop leaks through.
๐ก๏ธ First Layer of Defense: Edge Middleware (The Gatekeeper)
Imagine your website as an exclusive members-only club.
Middleware is like the concierge standing at the club's entrance. When a guest (HTTP Request) tries to enter the "VIP area" (/dashboard or /courses), the concierge instantly scans their name tag (Cookies).
If they don't have a tag (not logged in), the concierge won't let them waste resources inside and will promptly redirect them to the "registration counter" (/login).
Implementing Middleware
In Next.js, Middleware is a special file. It must be placed in the root directory (or the root of the src folder) and named exactly middleware.ts.
Create src/middleware.ts:
import { auth } from "@/auth"
import { NextResponse } from "next/server"
// Since Middleware runs on Vercel Edge Runtime (edge computing nodes),
// it is extremely lightweight and fast but cannot directly connect to complex PostgreSQL databases.
// The auth() function here decrypts the user's Cookie to determine their login status.
export default auth((req) => {
const isLoggedIn = !!req.auth;
// Get the full URL path the user is trying to access
const { pathname } = req.nextUrl;
// Define an array: Which route prefixes are absolutely protected?
const protectedRoutes = ["/dashboard", "/courses/premium", "/settings"];
// Check if the current path matches any protected routes
const isProtectedPath = protectedRoutes.some(route => pathname.startsWith(route));
// Interception logic: If you try to access a protected area without logging in
if (isProtectedPath && !isLoggedIn) {
// Not only redirect to the login page but also remember their original destination (callbackUrl)
// so they can automatically return after successful login!
const encodedCallback = encodeURIComponent(pathname);
const redirectUrl = new URL(`/login?callbackUrl=${encodedCallback}`, req.url);
// Force a 307 redirect
return NextResponse.redirect(redirectUrl);
}
// Additional logic: If a logged-in user tries to access /login, redirect them to the homepage
if (pathname === "/login" && isLoggedIn) {
return NextResponse.redirect(new URL("/", req.url));
}
// If all checks pass, proceed to the next step
return NextResponse.next();
})
// Configuration (Optimizer): Tell Next.js not to intercept static files to save computation costs
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - Public image files (e.g., .png, .jpg)
*/
'/((?!api|_next/static|_next/image|favicon.ico|.*\\.png$).*)',
],
}
๐ก Vibe Coding Testing Tip:
After writing the above, open an incognito window and directly enterhttp://localhost:3000/dashboardin the address bar.
You'll notice the page instantly redirects to/loginwith almost no delay. This is the pinnacle of Edge computing performance, protecting your server from unauthorized traffic.
๐ฐ Second Layer of Defense: Server Component Database Lock (Internal Vault)
While Middleware is fast, it has a critical flaw: It only knows if you're logged in, not if you've paid.
If a hacker registers a free account and obtains a standard member Cookie, they can easily bypass the Middleware gatekeeper and waltz into /courses/premium.
This is where we need a second absolute defense in the actual rendering layer (Server Component).
Open your premium course page src/app/courses/premium/page.tsx:
import { auth } from "@/auth"
import { redirect } from "next/navigation"
import { createClient } from "@/utils/supabase/server"
// Since this is a Server Component, this code runs on the server side.
// Hackers on the browser can never see this logic!
export default async function PremiumCoursePage() {
// 1. Get the user's session
const session = await auth()
// Even though Middleware already blocked unauthorized access, backend must verify again (defense in depth)
if (!session?.user) {
redirect("/login")
}
// 2. Fetch the user's ID and query their most private payment status from the database
const supabase = createClient()
const { data: userProfile, error } = await supabase
.from("users")
.select("membership_level, is_banned")
.eq("id", session.user.id)
.single()
// 3. Implement strict business logic interception
if (error || !userProfile) {
throw new Error("Failed to fetch user profile")
}
if (userProfile.is_banned) {
return <div className="text-red-500">Your account has been suspended. Please contact support.</div>
}
if (userProfile.membership_level !== 'VIP' && userProfile.membership_level !== 'Pro') {
// The user hasn't paid! Redirect them to the checkout page
return (
<div className="flex flex-col items-center justify-center h-[50vh] space-y-4">
<h1 className="text-2xl font-bold">Unlock Premium Business Tutorials</h1>
<p>This is a high-value course priced at 1999. Your current access level is insufficient.</p>
<a
href="/pricing"
className="bg-primary text-white px-6 py-3 rounded-lg shadow-lg hover:scale-105 transition-transform"
>
Upgrade to VIP Now
</a>
</div>
)
}
// 4. All checks passed! Finally, render the expensive business secrets
return (
<div className="p-8 max-w-4xl mx-auto">
<h1 className="text-4xl font-extrabold mb-4">๐ Welcome to the Elite Business Code Vault</h1>
<p className="text-xl text-gray-600 mb-8">Dear {session.user.name}, welcome!</p>
{/* Place your video player or confidential source code download link here */}
<div className="aspect-video bg-black rounded-2xl flex items-center justify-center text-white/50 text-2xl shadow-2xl">
Protected 4K tutorial video plays here
</div>
<div className="mt-8 p-6 bg-yellow-50 border border-yellow-200 rounded-xl">
<h3 className="font-bold text-yellow-800">Confidential Source Code Download:</h3>
<a href="/api/download/source-code.zip" className="text-blue-600 underline">
Click to download the 3999-tier exclusive source code
</a>
</div>
</div>
)
}
๐ก Architectural Insight: Why the Redundancy?
Early SPA (Single Page Application, e.g., pure React/Vue) architectures often made a critical mistake:
"Check if the user is VIP on the frontend, then use display: block or display: none."
For anyone with basic Chrome DevTools knowledge, simply pressing F12 to modify CSS or intercepting API responses to change is_vip: true would instantly bypass your paywall.
But in the App Router's Server Component architecture, if the user fails the server-side if checks, the server won't even compile the video or download links into the HTML!
Hackers can smash F12 or crash their browsers, but they'll never access data that was never sent. This is the essence of "backend absolute defense."
๐ Course Graduation Summary
Fantastic! These five chapters were a challenging journey, but you made it!
Let's recap the hacker-level skills you now possess:
- OAuth 2.0 Mastery: You understand the underlying token exchange logic and won't fear security questions in interviews.
- Multi-Platform Integration: You can confidently navigate complex developer dashboards like Google and Line, correctly setting up Callback URLs.
- Database Architecture Thinking: You've upgraded volatile Cookies into a rock-solid Supabase Database Session architecture.
- Enterprise-Grade Security: You've mastered the dual-defense strategy of Edge Middleware interception and Server Component backend locking.
The login system you've built is now more secure, faster, and more modern than 80% of those developed by freelance agencies.
Use this knowledge wisely to create more SaaS platforms where users feel safe paying! See you in the next advanced project!
Summary
Route protection in Auth.js is achieved through middleware. The getToken() function reads the JWT session without a database call, making it efficient for serverless environments. Protect API routes and pages with simple checks, and redirect unauthenticated users to the login page.
Key takeaways:
- Middleware runs before the page loads, protecting routes at the edge
getToken()reads the JWT session without a database call- Use
matcherconfig to specify which routes to protect - Redirect unauthenticated users to the sign-in page
- For database sessions, use
getServerSession()in API routes - Middleware works with both JWT and database sessions
- Protect API routes with session checks inside the handler
What's Next: Stripe International Payment
The next course covers Stripe payment integration โ checkout, webhooks, refunds, and international payment processing.