首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau
百百 BLOG
首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau

Kurau Blog

「隨心而寫,真真假假,都是我」

一個記錄生活、輸出興趣的個人空間。
遊戲、影視、閱讀、學習……每一段體驗都值得留下文字。

頁面導覽

  • 學習紀錄
  • 遊戲心得
  • 影視Life
  • 書單
  • 委託作品與二創
  • Kurau
  • 合作邀請

找到我

歡迎來 Discord 找我聊天!

“曾經發生的事不可能忘記,只是暫時想不起來而已。”-《神隱少女》

© 2026 Kurau All rights reserved

資安

限制特定網頁

By Kurau·2023-04-10·Updated 2026-05-09·6 分鐘閱讀

限制特定網頁(Auth 路由保護)

TL;DR
前端 router guard 只是 UX 改善,不是真正的安全層。真正的保護必須在 server side(SSR getServerSideProps / App Router middleware / API route 驗證 token)。CSR-only 的 router guard 任何人都繞得過。2026 推薦:Next.js App Router 的 middleware.ts 統一處理。
主要參考
Protected Routes in Next.js — Danish Shakeel

核心觀念:前端限制 ≠ 真安全

使用者輸入 /admin URL
        │
        ↓
   ┌─────────────────┐
   │  你的網頁載入    │  ← ==駭客已經拿到頁面 HTML / JS==
   └─────────┬───────┘
             ↓
   ┌─────────────────┐
   │  router guard    │  ← 太晚了,可以禁用 JS 跳過
   │  router.push    │
   │  ('/login')     │
   └─────────────────┘

router guard 在 client 端跑,就一定可以被繞過:

  • 禁用 JS
  • 改 source map / debugger 中斷 redirect
  • 直接 fetch API endpoint 拿資料(如果 API 沒驗證)

所以 router guard 只是「友善地把人擋下」,真實安全要靠 server。


4 種 Render 模式的保護策略

CSR(Client-Side Rendering)— 最不安全

// 純 client guard,只是 UX
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

export default function AdminPage() {
  const router = useRouter();

  useEffect(() => {
    const token = localStorage.getItem('token');
    if (!token || !verifyTokenLocally(token)) {
      router.push('/login');
    }
  }, [router]);

  return <div>Admin Content</div>;
}
tsx

問題:

  • 頁面 HTML / JS bundle 已下載,駭客能看 source code
  • 禁用 JS 就跳過 redirect
  • 真正的 admin data 應該 server 驗證後才回傳,前端只是「可以看到 UI」
CSR-only 真實漏洞
如果 admin data 透過 client-side fetch 拿到,駭客可以:
  1. 偽造 token / 改 token payload
  2. 直接打 API endpoint
  3. 拿到資料(因為 API 沒驗證)

根本解:API endpoint 必須在 server 驗證 token,而不是相信 client 帶來的 token。


SSR(Server-Side Rendering)— 最安全

// pages/admin.tsx(Pages Router)
import { GetServerSideProps } from 'next';
import jwt from 'jsonwebtoken';

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const token = ctx.req.cookies['session'];   // ⭐ 從 cookie 讀,不是 localStorage

  if (!token) {
    return { redirect: { destination: '/login', permanent: false } };
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!);
    return { props: { user: decoded } };
  } catch {
    return { redirect: { destination: '/login', permanent: false } };
  }
};

export default function AdminPage({ user }) {
  return <div>Welcome {user.name}</div>;
}
typescript

為什麼最安全:

  • 驗證在 server 端,駭客碰不到 secret
  • 失敗直接 302 redirect,使用者根本沒拿到 admin HTML
  • cookie 用 httpOnly,JS 偷不走 token

App Router(Next.js 13+)— 現代首選

方案 A:Middleware 統一處理

// middleware.ts(專案根目錄)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const PROTECTED_PATHS = ['/admin', '/dashboard', '/profile'];

export async function middleware(request: NextRequest) {
  const path = request.nextUrl.pathname;
  const needsAuth = PROTECTED_PATHS.some((p) => path.startsWith(p));
  if (!needsAuth) return NextResponse.next();

  const token = request.cookies.get('session')?.value;
  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  try {
    await verifyJWT(token);   // 你的驗證邏輯
    return NextResponse.next();
  } catch {
    return NextResponse.redirect(new URL('/login', request.url));
  }
}

export const config = {
  matcher: ['/admin/:path*', '/dashboard/:path*', '/profile/:path*'],
};
typescript

middleware 在 edge runtime 跑,所有 request 進來前先過這層。最簡潔的做法。

方案 B:在 Server Component 直接驗證

// app/admin/page.tsx — Server Component
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { verifyJWT } from '@/lib/auth';

export default async function AdminPage() {
  const token = cookies().get('session')?.value;
  if (!token) redirect('/login');

  try {
    const user = await verifyJWT(token);
    return <div>Welcome {user.name}</div>;
  } catch {
    redirect('/login');
  }
}
tsx

Server Component 直接 await 驗證,失敗 redirect。比 Pages Router 的 getServerSideProps 簡潔。


SSG(Static Site Generation)— 特殊處理

問題:SSG 在 build 時 預生成 HTML,根本沒有 request 概念,不能在 build 時驗證使用者。

只能在 client 加 guard:

// app/protected/page.tsx
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';

export default function ProtectedPage() {
  const router = useRouter();
  const [authed, setAuthed] = useState(false);

  useEffect(() => {
    fetch('/api/verify-session')   // ⭐ 由 API 驗證,不是 client 自己驗
      .then((r) => r.ok ? setAuthed(true) : router.push('/login'))
      .catch(() => router.push('/login'));
  }, [router]);

  if (!authed) return <Skeleton />;
  return <div>Protected content</div>;
}
tsx

重點:client 不自己驗,打 server endpoint 驗。server 才能讀 httpOnly cookie 跟 JWT secret。

SSG 是否該保護?
SSG 適合純公開內容(部落格、行銷頁、文件)。如果頁面需要保護,改用 SSR 或 ISR。硬要 SSG + client guard 是 anti-pattern。

ISR(Incremental Static Regeneration)— 介於 SSG 跟 SSR

類似 SSG,只是每隔一段時間 server 重新生成。保護策略跟 SSG 一樣(client guard + API 驗證)。


完整流程:從登入到保護路由

1. User 登入
   client → POST /api/login { email, password }
   server 驗證 → set httpOnly cookie 'session'
   server → 200 OK

2. User 訪問 /admin
   browser 自動帶 cookie
   middleware.ts / Server Component 讀 cookie
   驗證 OK → 顯示頁面
   驗證失敗 → 302 /login

3. User 打 /api/admin/users(資料 endpoint)
   browser 自動帶 cookie
   API route 讀 cookie 驗證
   通過 → 回資料
   失敗 → 401 Unauthorized

每一層都驗證 — middleware + server component + API route 都要驗,因為駭客可以繞過任一層直接打另一層。


HOC 寫法(Pages Router 經典)

// hocs/withAuth.tsx
import { GetServerSideProps } from 'next';
import jwt from 'jsonwebtoken';

export function withAuth(getServerSidePropsFunc?: GetServerSideProps): GetServerSideProps {
  return async (ctx) => {
    const token = ctx.req.cookies['session'];
    if (!token) {
      return { redirect: { destination: '/login', permanent: false } };
    }

    try {
      const user = jwt.verify(token, process.env.JWT_SECRET!);

      // 如果有原本的 getServerSideProps,呼叫它並合併 user
      if (getServerSidePropsFunc) {
        const result = await getServerSidePropsFunc(ctx);
        if ('props' in result) {
          return { props: { ...result.props, user } };
        }
        return result;
      }

      return { props: { user } };
    } catch {
      return { redirect: { destination: '/login', permanent: false } };
    }
  };
}

// 使用
import { withAuth } from '@/hocs/withAuth';

export const getServerSideProps = withAuth(async (ctx) => {
  const data = await fetchData(ctx.params!.id);
  return { props: { data } };
});

export default function AdminPage({ user, data }) {
  return <div>...</div>;
}
tsx

包裝後的 getServerSideProps 自動處理 auth,業務邏輯只管自己的事。


NextAuth.js / Auth.js(主流方案)

2026 年首選 auth library:

npm install next-auth
bash
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

const handler = NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
});

export { handler as GET, handler as POST };
typescript
// 在 Server Component 取 session
import { getServerSession } from 'next-auth';

export default async function AdminPage() {
  const session = await getServerSession();
  if (!session) redirect('/login');
  return <div>Welcome {session.user?.name}</div>;
}
tsx

NextAuth.js 處理 :OAuth、JWT、Session、Cookie security、CSRF 防禦。省 99% 自己寫的麻煩。


安全性對照總結

渲染模式Auth 在哪驗安全性適合
SSR / Middlewareserver⭐⭐⭐⭐⭐dashboard、私人頁
Server Components(App Router)server⭐⭐⭐⭐⭐同上,2026 推薦
ISR + client guardclient⭐⭐⭐半公開頁(部分 user 限定)
SSG + client guardclient⭐⭐不該保護 SSG
CSR onlyclient⭐不該用於需保護的頁
黃金法則
  1. Auth 永遠在 server 驗證
  2. token 用 httpOnly cookie,不是 localStorage
  3. API endpoint 也要驗(不能信 client request)
  4. middleware 統一處理(App Router)
  5. 正式環境用 NextAuth 而不是自己寫

常見地雷

1. 信任 client 帶來的 token
// ❌ API route 信 client decoded token
const user = req.body.user;
if (user.role === 'admin') { /* ... */ }
typescript

駭客可以亂改 body。必須在 server 重新驗 JWT signature。

2. JWT alg: none 攻擊
古老 JWT lib bug:接受 { "alg": "none" } 的 token,跳過 signature 驗證。

確保 lib 強制 algorithm:

jwt.verify(token, secret, { algorithms: ['HS256'] });   // ⭐ 必加
typescript
3. CSRF 沒擋
用 cookie auth 的 API 沒設 SameSite:
Set-Cookie: session=xxx; HttpOnly; Secure; SameSite=Strict
http

沒 SameSite 等於 允許其他網站偽造 request。

4. 在 Server Component 用 client-only auth
// ❌ Server Component 不能用 useAuth() hook
export default function Page() {
  const { user } = useAuth();   // 報錯
}
tsx

Server Component 用 getServerSession() / cookies(),不用 hook。

目錄

    ◆ 相關文章

    • cookie & sessionStorage加密 (crypto-js)

      2026-05-09
    • Termly 第三方cookie教學

      2026-05-09
    • 保護你的API KEY (React 前端)

      2026-05-09
    ← 上一篇Type 和 Interface的差別下一篇 →語言切換

    ◆ 關於作者

    Kurau

    個人寫作 / 創作的 SoT,記錄遊戲、影視、學習與生活。

    更多 Kurau 的文章