Yu Lin Chen (霖LiN)
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Authentication, Authorization and Security 簡介 Express RESTful API Server 範例中 (使用 MongoDB ODM Mongoose) * 利用 JWT 做驗證 (Authentication) * 利用自己定義的 User roles 和 permissions 做授權 (Authorization) * 利用一些現行可防止常見的安全性問題的 middleware、密碼做處理 (Security) [👉 GitLab Clone](https://gitlab.baifu-tech.net/f2e_tw/hr-api-server) ## [JSON Web Token (JWT)](https://jwt.io/) 簡介 > 有限時間內可利用認證令牌要求對應的API操作權限 * 基於 JSON Object 的開放標準協議 [RFC 7519](https://tools.ietf.org/html/rfc7519) * 適用於授權和訊息交換 * 單一登錄 (Single Sign On) 是當今廣泛使用 JWT 的功能之一,成本較小且可以在不同的 domain 中輕鬆使用 * 符合 Stateless 無狀態 (payload 中直接給 Server 需要的值,JWT應能表明身份) * 易水平擴展,適用於跨伺服器、跨域的請求 * Token 應只能在 Server 端被驗證,並 **store JWT in HTTPOnly cookies** * Cookie 只限被伺服端存取,無法在用戶端讀取 * 抵禦攻擊者利用 Cross-Site Scripting (XSS) 手法來盜取用戶身份,例如 `document.cookie` 取得 cookies * Chrome 有像 [EditThisCookie](https://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg) 的查看/修改 Cookie 工具 * 適用於無關安全議題的操作權限授與 * 無法取代 Cookies 或 Session,只是一種新的操作權限的授與方法 * Session、Cookie、JWT 是可以一起使用的 * 可以做為實現 **OAuth 2.0 授權框架規範** [RFC 6749](https://tools.ietf.org/html/rfc6749) 中的一種認證機制使用 ### JWT 組成 (這邊都指 JWS Compact Serialization) > [JWS(JSON Web Signature)](https://tools.ietf.org/html/rfc7515) 和 [JWE(JSON Web Encryption)](https://tools.ietf.org/html/rfc7516) 是 JWT 的實作方式,這邊簡介和實作範例都是 JWS (Compact Serialization):小、自包含、最簡易👻 ![](https://i.imgur.com/8ny9Nla.png) ![](https://i.imgur.com/MbEmWgj.png) JWT 由 header、payload、signature 三個部分各自 base64 編碼處理後組成 * header:伺服器如何加解密的依據 ```json { "alg": "HS256", // ALGORITHM: required "typ": "JWT" // TOKEN TYPE: optional } ``` * payload (claims):放認證需要的聲明內容 ```json { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } ``` * Reserved (註冊聲明) - 建議但不強制使用 * iss (issuer):發行人 * sub (subject):主題 (用戶) * aud (audience):目標收件人 * 通常為 URI 清單,表示此 JWT 可存取該清單中的資源 (位址) * exp (expiration time):到期的時間 * nbf (not before time):開始有效的時間 * iat (issued at time):發布時間 * jti (JWT ID) * Public (公開聲明) - [IANA JSON Web Token](https://www.iana.org/assignments/jwt/jwt.xhtml#claims) 聲明註冊表上註冊的聲明 * Private (私有聲明) - 自定義的臨時聲明 * Verify Signature:登入時伺服器端找到吻合的帳戶密碼,就產生一個認證簽名 * 可以比對 Signature 來確認 data 有無被改動 * 通常會產 Access Token 和 Refresh Token 兩種 Token (此次介紹屬於 Access Token) * 稍微提一下 * header 又稱 JOSE Header (JSON Object Signing and Encryption),[JWE](https://tools.ietf.org/html/rfc7518#section-3.1) 和 [JWS](https://tools.ietf.org/html/rfc7518#section-4.1) 適用的 alg 不相同 * JWE token 不同於分三段的 JWS,JWE 是分成五段:JOSE Header、Encrypted Key、Initialization Vector(IV)、AAD、Ciphertext、Authentication Tag * 其中 JOSE Header 是三種 Header 聯集而成: Protected Header + Shared Unprotected Header + Per-Recipient Unprotected Header ### 產生 JWS 流程 header 和 payload 是可以自行輕易 decode 的 (或直接貼[官網](https://jwt.io/)解) ,不應放任何敏感資訊 1. 讀取 header 中的加密演算法 (`alg`: 必要) 例如 HMAC、SHA256、RSA、none * 列消息認證碼 Hash-based Message Authentication Code (HMAC) 一種金鑰式雜湊演算法,可以結合加密金鑰 (key) 進行加密最後輸出 64 字元的內容,針對各種哈希算法都通用 (MD5, SHA-1, SHA-256...) * HS256 (HMAC with SHA-256) * `alg: none` 時 JWT 將不用產生 Signature,就有機會被攻擊來繞過 Token 的來源驗證 2. 建立尚未簽名的令牌 將 header 與 payload 各自進行 base64 編碼並且以`.`連結得到 3. 取得簽名令牌 signature 利用私鑰 key 對尚未簽名的令牌進行加密簽名得到 ⚠️ 絕對要保管好 key,不然誰都可以自行產生 JWT 4. 將 2. 與 base64 編碼後的 3. 結果以`.`連結,即為 JWT token ![](https://i.imgur.com/oMQboKh.png) ### 實作:Express app 中可以透過 [`jsonwebtoken`](https://www.npmjs.com/package/jsonwebtoken) 產生及驗證 JWT(JWS) * `npm i jsonwebtoken` * 簽發 token 時機:通常會在使用者註冊、登入、更新密碼時產生或更新 JWT Token 來做身份認證 (ps. 用戶被移除時也該讓 Token 失效) `jwt.sign(payload, secretOrPrivateKey, [options, callback])` * options 的 algorithm 預設為 HS256 * Ex. 簽發一個一小時後過期的 JWT token (多種寫法沒有一定) ```javascript= import jwt from 'jsonwebtoken'; // 1. jwt.sign({ exp: Math.floor(Date.now() / 1000) + (60 * 60), data: 'foobar' }, 'secret'); // 2. jwt.sign({ data: 'foobar' }, 'secret', { expiresIn: 60 * 60 }); // 3. 最好懂 👍 jwt.sign({ data: 'foobar' }, 'secret', { expiresIn: '1h' }); ``` 實際可能封裝成像 signToken function 每次簽發時使用 ```javascript= import jwt from 'jsonwebtoken'; const signToken = id => { return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN }); }; ``` * 認證 Token 若伺服器端在請求中沒有找到 Token,回傳錯誤 (401 Unauthorized);若有找到 Token 則驗證後再執行操作 `jwt.verify(token, secretOrPublicKey, [options, callback])` ```javascript= import { promisify } from 'util'; import jwt from 'jsonwebtoken'; // ... const decoded = await jwt.verify(token, process.env.JWT_SECRET); // decoded.id ``` ```jsonld // decoded { id: '5ee38288007f218942c9bd1b', iat: 1592380082, exp: 1600156082 } ``` * 只認令牌不認人,==只能**盡量**確保 key 不會被盜取== * 最好不要將 token 存在 localStorage (容易被 XSS 攻擊竊取) * 只在 HTTPS 安全協定下傳遞 * Cookie 設定 flag `httpOnly` (無法被 JavaScript 存取), `secure` (只在 HTTPS 傳遞) * 設置 token 過期時間... etc. * Ex. 如註冊和登入時去產生新的 JWT token 並透過盡量安全的方式送給 client (client 獲得令牌獲得授權) * Client 之後再將此令牌 (JWT) 設置在 Header Authorization 去要求 API,Server 再依此做認證,成功的話返回資料 ```javascript= const createSendToken = (user, statusCode, res) => { const token = signToken(user._id); const cookieOptions = { expires: new Date( Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000 ), httpOnly: true // cookie 無法用 JavaScript 讀取但仍可以在 HTTP requests 回傳給 server }; if (process.env.NODE_ENV === 'production') cookieOptions.secure = true; // 限 HTTPS res.cookie('jwt', token, cookieOptions); delete user.password; res.status(statusCode).json({ status: 'success', token, data: { user } }); }; ``` * 在某些需授權(認證通過)才能操作的 API route 前加一層 Middleware (要 return `next()`) ```javascript router.patch('/updateMe', authController.protect, userController.updateMe); router.delete('/deleteMe', authController.protect, userController.deleteMe); ``` 檢查認證:預期 Client 發的 request Headers Authorization 會代上合法 Token & ... ```javascript= // 需要授權的 API 先經過此做 JWT token 認證 (作為 protected route middleware) const protect = catchAsync(async (req, res, next) => { let token; if ( req.headers.authorization && req.headers.authorization.startsWith('Bearer') ) { token = req.headers.authorization.split(' ')[1]; } if (!token) { return next( new AppError('認證已失效,請重新登入', 401) ); } // 認證 JWT (token key 要正確才過) 取回 payload (可以檢查 JWT 授權者身份或資料有無變造) const decoded = await jwt.verify(token, process.env.JWT_SECRET); const currentUser = await User.findById(decoded.id); if (!currentUser) { return next( new AppError( '擁有此認證的使用者已不存在', 401 ) ); } // 檢查 token 簽發時間是否在變更密碼時間之後,是的話應重新登入取得新認證 if (currentUser.changedPasswordAfter(decoded.iat)) { return next( new AppError('使用者最近變更密碼,請重新登入', 401) ); } // 上述檢查沒問題才真正執行 API 查詢操作 (順便傳遞 JWT 的 user) req.user = currentUser; next(); }); ``` ## 密碼做 Bcrypt 加密處理再儲存 * Bcrypt 簡介 * 其實不是加密演算法,而是慢雜湊演算法 (和 SHA1 一樣是種雜湊演算法) * 把各個欄位/字元丟進去某個公式計算的方式就叫做雜湊 (Hash),這個計算公式就稱為雜湊函數 (Hash function),過程是**不可逆**的 * Bcrypt 可以透過設定疊代次數讓他變慢 (迭代次數每增加 1 需要的時間就變兩倍) * 密碼被破解的風險比 MD5、SHA1 低 (例以疊代五次的 Bcrypt 計算速度大概比 SHA1 慢 1000 倍) * 能夠將一個字串**加鹽後雜湊**加密,在要加密的字串中加特定的字符、打亂原始的字符串,使其生成的散列結果產生變化,加鹽次數多越安全,但加密時間也就越長 加密後的 bcrypt 分為四個部分 ![](https://i.imgur.com/Wp3VHdi.png) 1. Bcrypt:該字串為 UTF-8 編碼,並且包含一個終止符 1. Round 回合數:每增加一次就加倍雜湊次數,預設 10 次 1. Salt 加鹽:128 bits 22個字元 1. Hash 雜湊:138 bits 31個字元 * 防止 rainbow table attacks * Rainbow table 是一個由大量純文本密碼和與每個密碼相對應的 hash 組成的庫 * [Ultimate Hashing and Anonymity toolkit](https://md5hashing.net/) 可輕易破解長度短簡易的雜湊原文 * [`npm i bcryptjs`](https://www.npmjs.com/package/bcryptjs) * [`hash(s, salt, callback, progressCallback=)`](https://www.npmjs.com/package/bcryptjs#hashs-salt-callback-progresscallback) 在 mongoose 有 password field 的 Schema 上做 save document 前的 middleware 來處理明文密碼範例 ```javascript= userSchema.pre('save', async function (next) { if (!this.isModified('password')) return next(); // 只有在 password 修改時才執行 this.password = await bcrypt.hash(this.password, 12); // 用長度12的salt去hash this.passwordConfirm = undefined; this.passwordChangedAt = Date.now() - 1000; next(); }); ``` * 補充一點加密演算法,如: * 對稱加密演算法 * 加密解密都用同一個 key,快且安全 * 例 * AES (Advanced Encryption Standard) * DES (Data Encryption Standard)、3DES (Triple DES) * 速度:AES > 3DES > DES * 聽說美國政府機密檔案也用 AES 加密(?) * 非對稱加密演算法 * 會產生一組兩個 Key:公鑰跟私鑰 * 私鑰可以產出公鑰、公鑰無法產出私鑰 * 兩把鑰匙在加密、解密上彼此可通用:公鑰加密、私鑰解密,或是私鑰加密、公鑰解密 (HTTPS 的 SSL 數位簽章就是此應用) * 例 * RSA、DSA (Digital Signature Algorithm)、ECC (Elliptic Curves Cryptography) * 速度:ECC > RSA, DSA ![](https://i.imgur.com/m2s3GgW.png) ## 訂定 Role 來限制權限 * 例如 User Model 中的 Schema 定義一 enum 型別的 role,之後依此做權限依據 * 某些需檢查使用者權限才能操作的 API route,可在操作前加一層檢查 user 權限的 middleware ```javascript= router .route('/:id') .get(tourController.getTour) .patch(tourController.updateTour) .delete( authController.protect, authController.restrictTo('admin', 'lead-guide'), tourController.deleteTour ); ``` ```javascript= const restrictTo = (...roles) => { return (req, res, next) => { if (!roles.includes(req.user.role)) { return next( new AppError('您沒有此操作權限', 403) ); } next(); }; }; ``` ## 一些跟安全性相關的 Middleware library > Express mongoose restful api 實作用到 * [`npm i helmet`](https://www.npmjs.com/package/helmet) * A collection of 12 smaller middleware functions that set HTTP response headers. Express app 中的安全最佳做法,會適當設定 HTTP 標頭,有助於防範應用程式出現已知的 Web 漏洞 * 應放在最一開始設定 ```javascript app.use(helmet()); ``` * [`npm i express-rate-limit`](https://www.npmjs.com/package/express-rate-limit) * 限制來自同一IP的重複請求 * 可防止 DDoS ```javascript= import rateLimit from 'express-rate-limit'; const limiter = rateLimit({ max: 100, windowMs: 60 * 60 * 1000, message: '此IP請求太多次了!請一小時後再試!' }); app.use('/api', limiter); ``` * [`npm i express-mongo-sanitize`](https://www.npmjs.com/package/express-mongo-sanitize) * Data sanitization 防止 NoSQL query injection,這邊主要針對 MongoDB 的保留字如 `$`, `.`。例如在某含有 find email 操作的 api 中 request body 代相當於 query all 的值 `{ "email": {"$gt": ""} }`,就會被擋下來 ```javascript app.use(mongoSanitize()); ``` * [`npm i xss-clean`](https://www.npmjs.com/package/xss-clean) * Data sanitization 防止 XSS 攻擊,可替換 `<` 為 `&lt;` ```javascript app.use(xss()); ``` * [`npm i hpp`](https://www.npmjs.com/package/hpp) * 用於防止 HTTP Parameter Pollution * 例如網址後面代的 queryString key 相同的不只一個時,它會自動取成最後一個、前面都無效 * 但是可以透過把特定 key 加白名單讓它得以不被過濾 * 應該在最後設定 ```javascript\ app.use( hpp({ whitelist: ['age', 'team'] }); ); ``` ## 重設密碼機制 利用賦予請求重設密碼用戶者 隨機 resetToken 與 Token 過期時間儲存進 DB (同時寄信),並在更新密碼時實作中用來驗證並透過 Token 查回 User 做更新 * [`npm i nodemailer`](https://www.npmjs.com/package/nodemailer) * 開發階段沒有 Mail Server 可以使用 [MailTrap](https://mailtrap.io/) 測試寄收信 ![](https://i.imgur.com/yBu0U7l.png) * 利用 Node.js 的內建模組 crypto 來簡單加密處理資料,以下實作範例 1. 在 Model Schema methods 定義產生密碼重設 Token 並設置過期時間存 DB user 方法:注意是 return 未 Hash 的 Token (要代給修改密碼 API 的 Token),但儲存的是有在經過 hash 處理後的 Token ```javascript= // 透過 node 內建 crypto 安全的亂數產生密碼重設 token,並設置過期時間 userSchema.methods.createPasswordResetToken = function () { const resetToken = crypto.randomBytes(32).toString('hex'); this.passwordResetToken = crypto .createHash('sha256') // 創建sha256 hash實例 .update(resetToken) // update()將字符串相加 .digest('hex'); // digest()將字符串hash返回 this.passwordResetExpires = Date.now() + 10 * 60 * 1000; // 10 mins 後過期 return resetToken; }; ``` 2. 忘記密碼 API 查詢 email 的 User 存在用對它賦予重設密碼 Token 和過期時間儲存;不存在再清掉重設相關欄位,同時寄出代有該 Token 的重設密碼 API 給 User ```javascript= const forgotPassword = catchAsync(async (req, res, next) => { const user = await User.findOne({ email: req.body.email }); if (!user) { return next(new AppError('查無此 email 使用者', 404)); } // 產生 reset 用的隨機 token(Model schema methods 先定義好) const resetToken = user.createPasswordResetToken(); await user.save({ validateBeforeSave: false }); // 設定寄信內容 const resetURL = `${req.protocol}://${req.get('host')}/api/v1/users/resetPassword/${resetToken}`; const message = `忘記密碼?請用 PATCH 設置 password 和 passwordConfirm 請求 API: ${resetURL}.\n如果您無忘記密碼,請無視此訊息。`; try { await sendEmail({ email: user.email, subject: '密碼重設 (10分鐘後過期)', message }); res.status(200).json({ status: 'success', message: '重設密碼 Token 已寄出!' }); } catch (err) { user.passwordResetToken = undefined; user.passwordResetExpires = undefined; await user.save({ validateBeforeSave: false }); return next( new AppError('寄信發生錯誤!'), 500 ); } }); ``` ```javascript= import nodemailer from 'nodemailer'; export const sendEmail = async options => { // 1) Create a transporter const transporter = nodemailer.createTransport({ host: process.env.EMAIL_HOST, port: process.env.EMAIL_PORT, auth: { user: process.env.EMAIL_USERNAME, pass: process.env.EMAIL_PASSWORD } }); // 2) Define the email options const mailOptions = { from: '百阜人資系統 <baifu.hr@baifu-tech.net>', to: options.email, subject: options.subject, text: options.message // html: }; // 3) Actually send the email await transporter.sendMail(mailOptions); }; ``` 3. 得到重設密碼的 API 驗證 Token 和是否有效後再依 Token 查回 User,對它做密碼更新 ```javascript= const resetPassword = catchAsync(async (req, res, next) => { // hashed resetToken and compare document's hashed resetToken passwordResetToken const hashedToken = crypto .createHash('sha256') .update(req.params.token) .digest('hex'); const user = await User.findOne({ passwordResetToken: hashedToken, passwordResetExpires: { $gt: Date.now() } }); // 檢查 token 未過期且使用者存在 if (!user) { return next(new AppError('Token 無效或是已過期', 400)); } user.password = req.body.password; user.passwordConfirm = req.body.passwordConfirm; user.passwordResetToken = undefined; user.passwordResetExpires = undefined; await user.save(); // 一樣會經過 middleware 做密碼 hash 並修改 passwordChangedAt // 使用者產生新 token 回傳(登入) createSendToken(user, 200, res); }); ``` ## CSRF 因為瀏覽器的機制,你只要發送 request 給某個網域,就會把關聯的 cookie 一起帶上去。如果使用者是登入狀態,那這個 request 就理所當然包含了他的資訊(例如說 session id),這 request 看起來就像是使用者本人發出的。 又稱 One-Click Attack,但其實甚至不需要 click 而且不會被察覺(沒有 redirect) 例如 ```html= <iframe style="display:none" name="csrf-frame"></iframe> <form method='POST' action='https://small-min.blog.com/delete' target="csrf-frame" id="csrf-form"> <input type='hidden' name='id' value='3'> <input type='submit' value='submit'> </form> <script>document.getElementById("csrf-form").submit()</script> ``` > 所以記得每次使用完網站就登出,就可以避免掉 CSRF Server 防範 1. 檢查 Referer (request從哪來),但有的瀏覽器不會帶,或是被關掉 2. 加上圖形驗證碼、簡訊驗證碼 ... 3. 產生隨機的 CSRF Token 存在 Server Session,並核對請求者帶的 Session 是否一樣 4. Double Submit Cookie:Server 比對 Cookie 內的 CSRF Token 與 form 裡面的 CSRF Token,檢查是否有值並且相等 下次再補充 JWE (JSON Web Encryption)、 OAuth 2.0 的部分 # Reference * [React Authentication: How to Store JWT in a Cookie](https://medium.com/@ryanchenkie_40935/react-authentication-how-to-store-jwt-in-a-cookie-346519310e81) * [淺談JWT的安全性與適用情境](https://medium.com/mr-efacani-teatime/%E6%B7%BA%E8%AB%87jwt%E7%9A%84%E5%AE%89%E5%85%A8%E6%80%A7%E8%88%87%E9%81%A9%E7%94%A8%E6%83%85%E5%A2%83-301b5491b60e) * [是誰在敲打我窗?什麼是 JWT?](https://5xruby.tw/posts/what-is-jwt/) * [聽說不能用明文存密碼,那到底該怎麼存?](https://medium.com/starbugs/how-to-store-password-in-database-sefely-6b20f48def92) * [加密算法(DES,AES,RSA,MD5,SHA1,Base64)比較和項目應用](https://kknews.cc/zh-tw/code/kbqp4bb.html) * [關於Error.captureStackTrace](http://blog.shaochuancs.com/about-error-capturestacktrace/) * [Mongoose API Document](https://mongoosejs.com/docs/api.html)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully