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 支援哪些方法?
- 查看 API 文件
- 看原始碼中定義了哪些 export function
- 用 OPTIONS 請求查詢(如果有實作)
Q3:CORS 錯誤會顯示為 403 嗎?
有時候會。如果 preflight 請求失敗,可能顯示為 403。
解決方法是正確設定 CORS header。
Q4:Token 過期應該回傳 401 還是 403?
應該回傳 401。
因為過期的 Token 等同於無效的認證。
還是無法解決?
如果你嘗試了以上方法還是有問題:
1. 收集這些資訊
- 完整的錯誤訊息
- 請求的 URL 和方法
- 發送的 header
- API 的原始碼(如果可以)
2. 讓專業的來
認證問題有時候需要深入分析。
Vercel 部署失敗?
Build Error、環境變數、自訂網域,我們幫你快速排除問題。