Vercel 401 / 403 / 405 錯誤排解|權限與方法問題完整修復指南

Vercel 401 / 403 / 405 錯誤排解|權限與方法問題完整修復指南

API 回傳 401、403 或 405 錯誤?

這三種錯誤都和「權限」或「請求方法」有關。

不同的錯誤代碼代表不同的問題,解決方法也不同。

這篇文章教你怎麼診斷和修復這些錯誤。


4xx 錯誤代碼快速對照

先搞清楚每個錯誤的意思。

錯誤代碼 名稱 意思
401 Unauthorized 沒有提供認證資訊
403 Forbidden 認證了但沒有權限
405 Method Not Allowed 用了不支援的 HTTP 方法

簡單區分

  • 401:「你是誰?請先證明身份」
  • 403:「我知道你是誰,但你沒權限做這件事」
  • 405:「這個 API 不接受你用的方法(GET/POST/PUT 等)」

401 Unauthorized 錯誤解決方案

401 代表:請求沒有提供有效的認證資訊。

常見原因

1. 沒有傳送認證 Token

// ❌ 忘記加 Authorization header
const res = await fetch('/api/protected');

// ✅ 正確做法
const res = await fetch('/api/protected', {
  headers: {
    'Authorization': `Bearer ${token}`,
  },
});

2. Token 格式錯誤

// ❌ 格式錯誤
headers: { 'Authorization': token }

// ✅ 正確格式
headers: { 'Authorization': `Bearer ${token}` }

3. Token 過期

Token 有有效期限,過期後就會被拒絕。

4. API Key 錯誤

// ❌ API Key 打錯或用到舊的
const res = await fetch('/api/data', {
  headers: { 'X-API-Key': 'wrong-key' },
});

診斷方法

1. 檢查 Request Headers

用瀏覽器開發者工具(F12)→ Network,看請求有沒有帶正確的 header。

2. 確認 Token 有效

// 在前端 console 檢查 token
console.log('Token:', localStorage.getItem('token'));
console.log('Token length:', localStorage.getItem('token')?.length);

3. 測試 API

用 curl 或 Postman 直接測試 API,確認是前端問題還是後端問題。

解決方法

修復前端請求:

async function fetchProtectedData() {
  const token = localStorage.getItem('token');

  if (!token) {
    // 沒有 token,導向登入頁
    window.location.href = '/login';
    return;
  }

  const res = await fetch('/api/protected', {
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  });

  if (res.status === 401) {
    // Token 無效或過期,重新登入
    localStorage.removeItem('token');
    window.location.href = '/login';
    return;
  }

  return res.json();
}

修復後端驗證:

export async function GET(request) {
  const authHeader = request.headers.get('Authorization');

  if (!authHeader) {
    return Response.json(
      { error: 'Authorization header required' },
      { status: 401 }
    );
  }

  const token = authHeader.replace('Bearer ', '');

  try {
    const user = await verifyToken(token);
    // 繼續處理請求
  } catch (error) {
    return Response.json(
      { error: 'Invalid or expired token' },
      { status: 401 }
    );
  }
}

403 Forbidden 錯誤解決方案

403 代表:認證成功了,但沒有權限執行這個操作。

常見原因

1. 用戶權限不足

// 用戶是普通用戶,但想存取管理員功能
// 後端會回傳 403

2. 資源存取限制

// 用戶想存取別人的資料
GET /api/users/123/private
// 如果當前用戶不是 123,會回傳 403

3. IP 或地區限制

某些 API 可能限制特定 IP 或地區存取。

4. CORS 問題(有時候會顯示為 403)

// 跨域請求被阻擋

診斷方法

1. 確認用戶權限

// 檢查用戶角色
console.log('User role:', user.role);
console.log('Required role:', 'admin');

2. 確認資源所有權

// 確認用戶是否有權存取該資源
console.log('Resource owner:', resource.userId);
console.log('Current user:', currentUser.id);

解決方法

前端處理 403:

async function fetchData() {
  const res = await fetch('/api/admin/data');

  if (res.status === 403) {
    // 顯示權限不足訊息
    alert('您沒有權限執行此操作');
    return;
  }

  return res.json();
}

後端權限檢查:

export async function GET(request) {
  const user = await getCurrentUser(request);

  if (!user) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  if (user.role !== 'admin') {
    return Response.json({ error: 'Forbidden' }, { status: 403 });
  }

  // 有權限,繼續處理
  const data = await getAdminData();
  return Response.json(data);
}

處理 CORS:

vercel.json 加入 CORS header:

{
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "*" },
        { "key": "Access-Control-Allow-Methods", "value": "GET, POST, PUT, DELETE, OPTIONS" },
        { "key": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization" }
      ]
    }
  ]
}

405 Method Not Allowed 錯誤解決方案

405 代表:API 不接受你使用的 HTTP 方法。

常見原因

1. 用錯 HTTP 方法

// ❌ API 只接受 POST,但你用 GET
const res = await fetch('/api/submit');  // 預設是 GET

// ✅ 正確使用 POST
const res = await fetch('/api/submit', {
  method: 'POST',
  body: JSON.stringify(data),
});

2. API Route 沒有定義該方法

// 在 Next.js 中,如果只定義了 GET
export async function GET() {
  return Response.json({ data: 'ok' });
}

// 那麼 POST 請求會得到 405

3. 表單預設行為

<!-- ❌ 表單預設用 GET -->
<form action="/api/submit">

<!-- ✅ 指定用 POST -->
<form action="/api/submit" method="POST">

診斷方法

1. 確認 API 支援的方法

查看 API 文件或原始碼,確認支援哪些方法。

2. 檢查 Network 請求

在瀏覽器開發者工具中,看實際發送的是什麼方法。

解決方法

確保使用正確的方法:

// 根據操作選擇方法
// 讀取資料:GET
// 建立資料:POST
// 更新資料:PUT 或 PATCH
// 刪除資料:DELETE

// 建立資料
const res = await fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'John' }),
});

// 更新資料
const res = await fetch('/api/users/123', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Jane' }),
});

// 刪除資料
const res = await fetch('/api/users/123', {
  method: 'DELETE',
});

在 API 中支援多種方法:

// Next.js App Router
export async function GET() {
  // 處理 GET 請求
}

export async function POST(request) {
  // 處理 POST 請求
}

export async function PUT(request) {
  // 處理 PUT 請求
}

export async function DELETE(request) {
  // 處理 DELETE 請求
}

處理 OPTIONS 請求(CORS preflight):

export async function OPTIONS() {
  return new Response(null, {
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    },
  });
}

進階:設定 API 認證

完整的 API 認證範例。

使用 JWT 認證

// middleware.ts
import { NextResponse } from 'next/server';
import { jwtVerify } from 'jose';

export async function middleware(request) {
  // 只對 /api/protected 開頭的路徑進行驗證
  if (!request.nextUrl.pathname.startsWith('/api/protected')) {
    return NextResponse.next();
  }

  const authHeader = request.headers.get('Authorization');

  if (!authHeader?.startsWith('Bearer ')) {
    return NextResponse.json(
      { error: 'Authorization required' },
      { status: 401 }
    );
  }

  const token = authHeader.split(' ')[1];

  try {
    const secret = new TextEncoder().encode(process.env.JWT_SECRET);
    const { payload } = await jwtVerify(token, secret);

    // 把用戶資訊加到 header,讓 API 可以取用
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-user-id', payload.userId);

    return NextResponse.next({
      request: { headers: requestHeaders },
    });
  } catch (error) {
    return NextResponse.json(
      { error: 'Invalid token' },
      { status: 401 }
    );
  }
}

使用 API Key 認證

// app/api/data/route.ts
export async function GET(request) {
  const apiKey = request.headers.get('X-API-Key');

  if (!apiKey) {
    return Response.json(
      { error: 'API key required' },
      { status: 401 }
    );
  }

  // 驗證 API Key(實際應該從資料庫查詢)
  const validKeys = process.env.VALID_API_KEYS?.split(',') || [];

  if (!validKeys.includes(apiKey)) {
    return Response.json(
      { error: 'Invalid API key' },
      { status: 401 }
    );
  }

  // API Key 有效,繼續處理
  return Response.json({ data: 'secret data' });
}

常見問題 FAQ

Q1:401 和 403 有什麼不同?

  • 401:沒有認證,或認證失敗
  • 403:已認證,但沒有權限

簡單說:401 是「你是誰」的問題,403 是「你能做什麼」的問題。

Q2:怎麼知道 API 支援哪些方法?

  1. 查看 API 文件
  2. 看原始碼中定義了哪些 export function
  3. 用 OPTIONS 請求查詢(如果有實作)

Q3:CORS 錯誤會顯示為 403 嗎?

有時候會。如果 preflight 請求失敗,可能顯示為 403。

解決方法是正確設定 CORS header。

Q4:Token 過期應該回傳 401 還是 403?

應該回傳 401

因為過期的 Token 等同於無效的認證。


還是無法解決?

如果你嘗試了以上方法還是有問題:

1. 收集這些資訊

  • 完整的錯誤訊息
  • 請求的 URL 和方法
  • 發送的 header
  • API 的原始碼(如果可以)

2. 讓專業的來

認證問題有時候需要深入分析。


Vercel 部署失敗?

Build Error、環境變數、自訂網域,我們幫你快速排除問題。

解決 Vercel 問題


延伸閱讀

分享文章:
V

VibeFix

專門解決 AI Vibe Coding 後的疑難雜症,讓你的專案順利上線。

這篇文章有幫到你嗎?

如果還有問題,讓我們直接幫你解決!

聯繫我們