昨天我們建立了 Admin 後台雛形與登入頁面,但目前的登入流程仍是假裝的(輸入什麼都能進 Dashboard)。
今天要導入真正的 身分驗證機制,利用 JWT(JSON Web Token) 或 Session,確保只有合法的管理員能登入後台。
我們會示範如何用後端驗證帳密,簽發 JWT,並在前端存取與帶入,完成基本的安全登入流程。
避免任何人都能直接打 API。
確保只有管理員能操作訂單。
是後台系統的基本安全需求。
Session:伺服器維護狀態,透過 Cookie 綁定。
JWT:Stateless,前端帶 Token,後端用密鑰驗證即可。
我們的系統更偏 API 架構 → 採用 JWT。
管理員在前端輸入帳密 → 呼叫 /auth/admin/login
。
後端驗證帳密 → 簽發 JWT。
前端把 JWT 存在 LocalStorage。
呼叫 API(如 /orders
)時加上 Authorization: Bearer <JWT>
。
npm i bcrypt jsonwebtoken cors
bcrypt
用途:安全地雜湊密碼(不可逆),登入時用「明碼 + 同樣的鹽」比對。
為什麼:不能把明碼或可逆加密儲存在 DB;bcrypt 內建鹽與延遲計算,能降低爆破成功率。jsonwebtoken (JWT)
用途:簽發/驗證 JSON Web Token,讓前端帶著 Token 打 API,後端驗證簽章與時效。
為什麼:Stateless 驗證,適合 API 架構與多服務。cors
用途:設定 跨來源資源共享,讓你的 Express API 可以被不同「來源(協定+網域+埠)」的前端呼叫。
為什麼:瀏覽器有同源政策;Vite 5173 要打 Express 3000,必須讓後端回應 CORS 標頭。
src/routes/auth.js
// src/routes/auth.js
const express = require("express");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const router = express.Router();
// (示範)硬編一個管理員帳號;正式應該放 DB
// 密碼:admin123
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || "admin@test.com";
const ADMIN_HASH = process.env.ADMIN_HASH || "$2b$10$2wqv7lq7i0v1u5cN3N5V7e2Qm4qVw9F3r1b0yC5XxYVQm0q8qA0we";
router.post("/login", async (req, res) => {
const { email, password } = req.body || {};
if (!email || !password) return res.status(400).json({ message: "缺少 email 或 password" });
if (email !== ADMIN_EMAIL) return res.status(401).json({ message: "帳號不存在" });
const ok = await bcrypt.compare(password, ADMIN_HASH);
if (!ok) return res.status(401).json({ message: "密碼錯誤" });
const token = jwt.sign(
{ role: "admin", email },
process.env.JWT_SECRET || "dev_secret_change_me",
{ expiresIn: "7d" }
);
res.json({ jwt: token });
});
// 共用:保護管理員 API 的 middleware
function authAdmin(req, res, next) {
const auth = req.headers.authorization || "";
const token = auth.startsWith("Bearer ") ? auth.slice(7) : null;
if (!token) return res.status(401).json({ message: "缺少 JWT" });
jwt.verify(token, process.env.JWT_SECRET || "dev_secret_change_me", (err, decoded) => {
if (err || decoded.role !== "admin") return res.status(403).json({ message: "驗證失敗" });
req.admin = decoded;
next();
});
}
module.exports = { router, authAdmin };
想用資料庫:可以改成
Admin
model(admin.model.js
)查 email 取passwordHash
,其餘不變。
src/index.js
掛上路由// src/index.js
const express = require("express");
const path = require("path");
const cors = require("cors");
const app = express();
app.use(cors({ origin: true, credentials: true }));
app.use(express.json());
// 提供 LIFF 靜態檔
app.use(express.static(path.resolve(__dirname, "..", "liff-form")));
// 你的既有訂單路由
app.use("/orders", require("./routes/order"));
// 這行是關鍵:把 Day18 的登入路由掛到 /auth/admin
const { router: authRouter } = require("./routes/auth");
app.use("/auth/admin", authRouter);
app.listen(3000, () => console.log("API on http://localhost:3000"));
async function handleLogin(e) {
e.preventDefault();
try {
const res = await axios.post("http://localhost:3000/auth/admin/login", {
email, password
});
localStorage.setItem("jwt", res.data.jwt);
window.location.href = "/dashboard";
} catch (err) {
alert("登入失敗");
}
}
const token = localStorage.getItem("jwt");
const res = await axios.get("http://localhost:3000/orders", {
headers: { Authorization: `Bearer ${token}` }
});
從圖片中可以發現,照上述做法操作完成後,會遇到典型的 CORS 問題,明天(Day 19)會詳細向大家說明這個問題原因並且提供解法!
Admin 後台不能只是「畫面」,必須加上 身分驗證。
JWT 適合 API 架構,讓我們能保護 /orders
等敏感路由。
明天(Day 19)我們修正完 CORS 問題後,會讓後台 串接 API,顯示訂單列表,讓管理員能真正操作訂單。
昨日把 Router 架構要放的位置打成 Main.jsx 了,應該要放在 App.jsx!