## interior_helper 開發-後端學習筆記 bcrypt 處理密碼 --- ```javascript bcrypt.genSalt(10) // 產生「鹽」,並設定複雜度係數為 10 .then(salt => bcrypt.hash(password, salt)) // 為使用者密碼「加鹽」,產生雜湊值 ``` ```javascript bcrypt.compare(password, user.password) ``` JWT 認證機制 --- cookie-based機制 / token-based機制 ![image](https://hackmd.io/_uploads/HJulOX_IJg.png) `cookie & session 的機制` * cookie:用戶端的憑證,有如會員卡,卡上有會員編號 (也就是 session_id) * session:伺服器端的憑證對照表,有如會員名冊,可以透過會員編號 (session_id) 查找到使用者資訊 (user_id) cookie 是瀏覽器存放身份憑證的儲存機制, session 是伺服器存放身份憑證的儲存機制。 ![image](https://hackmd.io/_uploads/HkCkIX_Uyl.png) 用戶端、應用程式伺服器、資料庫三個地方的資訊匹配起來 ![image](https://hackmd.io/_uploads/HJYsCQOUke.png) *有個問題點:cookie 的值只能在特定網域內被存取 沒辦法跨網域使用* --- `JWT Token` 由三部分組成:header.payload.signature * Header - 標記 token 的類型與雜湊函式名稱 * Payload - 要攜帶的資料,例如 user_id 與時間戳記,也可以指定 token 的過期時間 * Signature - 根據 Header 和 Payload,加上密鑰 (secret) 進行雜湊,產生一組不可反解的亂數,當成簽章,用來驗證 JWT 是否經過篡改 ```javascript Header(標頭): { "alg": "HS256", // 使用的加密算法 "typ": "JWT" // token 類型 } Payload(負載): { "id": "user_id", // 自定義數據 } Signature(簽名): HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), JWT_SECRET ) ``` --- res.cookie() 是 Express.js 中用於設置 HTTP Cookie 的方法 ```javascript { // 禁止客戶端 JavaScript 訪問此 Cookie httpOnly: true, // 只在 HTTPS 連接中傳輸 secure: process.env.NODE_ENV === 'production', // Cookie 的有效期(毫秒) maxAge: 30 * 24 * 60 * 60 * 1000, // 30天 // 可選:Cookie 的路徑 path: '/', // 可選:Cookie 的域名 domain: 'your-domain.com', // 可選:防止 CSRF 攻擊 sameSite: 'strict', } ``` 2. 身份驗證middleware: 用於驗證 token 的有效性和解碼 token 的內容 ```javascript= const decoded = jwt.verify(token, process.env.JWT_SECRET!) as JwtCustomPayload; ``` 區別 | JWT_SECRET | Token | | | ----- |:------------------------------------------ | -------- | |是用於加密/解密的密鑰 | 是加密後的結果 保存在服務器端 | 發送給客戶端保存 永遠不會發送給客戶端 | 客戶端每次請求都會發送 通常較短且簡單 | 可以被解碼查看內容(但無法偽造,因為沒有密鑰) | ```javascript= // 1. JWT_SECRET(在 .env 文件中) JWT_SECRET=myVerySecretKey123 // 2. 創建 token(在 login 函數中) const token = jwt.sign( { id: "123", email: "user@example.com", role: "user" }, process.env.JWT_SECRET!, // 使用密鑰進行簽署 { expiresIn: '7d' } ); // token 會是類似這樣的: // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c // 3. 驗證 token const decoded = jwt.verify(token, process.env.JWT_SECRET!) as JwtCustomPayload; // decoded 會是: // { // id: "123", // email: "user@example.com", // role: "user", // iat: 1516239022, // exp: 1516843822 // } ``` 在前端讀取 Cookie: ```javascript // 如果 Cookie 不是 httpOnly document.cookie // 可以讀取非 httpOnly 的 Cookie // 如果是 httpOnly Cookie,只能通過後端 API 訪問 fetch('/api/auth/user', { credentials: 'include' // 必須設置才能發送 Cookie }); ``` Authorization --- let token = req.headers('authorization') ```javascript= if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { token = req.headers.authorization.split(' ')[1]; } else if (req.cookies?.token) { token = req.cookies.token; } if (!token) { res.status(401).json({ message: 'Acess Denied' }); return; } const verified = jwt.verify(token, process.env.JWT_SECRET as string) as { id: string }; ``` auth.config.ts 與 auth.ts --- ```javascript= //auth.ts export const { auth, handlers: { GET, POST }, signIn, signOut, } = NextAuth({ adapter: PrismaAdapter(prisma), session: { strategy: 'jwt' }, ...authConfig, }) // auth.config.ts export default { providers: [ // 定義認證提供者 Credentials({ // 使用憑證(用戶名密碼)方式認證 ``` auth.ts:認證的主要實現檔案 整合了各種認證相關的功能: * 使用 PrismaAdapter 來處理資料庫操作 * 設定 session 策略為 JWT * 導入 auth.config.ts 的配置(通過 ...authConfig) --- 不需要安裝 body-parser,原因如下 ```javascript= // 這些行已經在你的 index.ts 中 app.use(express.json()); app.use(express.urlencoded({ extended: true })); ``` > 生成 Cloudinary 上傳所需的簽名 簽名的生成過程在服務器端完成,這樣可以保護 API 密鑰的安全性 ```javascript= export async function POST(request: Request) { const body = (await request.json()) as {paramsToSign: Record<string, string>} const {paramsToSign} = body; const signature = cloudinary.v2.utils.api_sign_request(paramsToSign, process.env.CLOUDINARY_API_SECRET as string); return Response.json({signature}); } ``` --- const isLoggedIn = !!req.auth 可以寫成 const isLoggedIn = req.auth 嗎? ```javascript= export default auth((req) =>{ const isLoggedIn = !!req.auth; ``` 雙重否定 (!!) 無論 req.auth 是什麼類型(例如 undefined、null、空字串、物件等),!! 確保 isLoggedIn 是布林值。 ```javascript= console.log(!!null); // false console.log(!!undefined); // false console.log(!!{}); // true console.log(!!''); // false ``` ```javascript ```