前提・実現したいこと
現在ExpressにてWebアプリケーションのAPIサーバーを開発しております。
csurfミドルウェアを使用してCSRF対策の実装自体は出来ているのですが、どこでcsrf-tokenを返すのか、どこにプロテクトをかけるのかといった部分にアドバイスを貰いたいです。
該当のソースコード
このようなCRUD機能を実装したAPIと
userjs
1const express = require('express'); 2const router = express.Router(); 3const bcrypt = require("bcrypt") 4const helper = require("../../helper") 5const documentClient = require("../../dbconnect") 6const geocoder = require("../../gecorderSetting") 7const { check, validationResult } = require('express-validator'); 8const csrf = require('csurf'); 9const csrfProtection = csrf({ cookie: false }); 10 11router.get("/show/:name", helper.authenticateToken, helper.adminUserCheck, csrfProtection, (req, res) => { 12 const params = { 13 TableName: "Timecards", 14 Key: { 15 user: req.params.name, 16 attendance: "user" 17 } 18 }; 19 documentClient.get(params).promise() 20 .then((result) => res.json({ "user": result.Item, "csrfToken": req.csrfToken() })) 21 .catch((e) => res.status(500).json({ errors : e})) 22 }) 23 24router.get("/index", helper.authenticateToken, helper.adminUserCheck, csrfProtection, (req, res) => { 25 const params = { 26 TableName: 'Timecards', 27 IndexName: 'usersIndex', 28 ExpressionAttributeNames: { '#a': 'attendance' }, 29 ExpressionAttributeValues: { ':val': 'user' }, 30 KeyConditionExpression: '#a = :val' 31 }; 32 documentClient.query(params, (err, result) => { 33 if (err) { 34 res.status(500).json({errors: err}) 35 } else { 36 res.json({ "users": result.Items,"csrfToken":req.csrfToken() }) 37 } 38 }) 39}); 40 41router.post("/signup", helper.authenticateToken, helper.adminUserCheck, csrfProtection, [ 42 check("username").not().isEmpty().matches("^[ぁ-んァ-ヶア-ン゙゚一-龠]*$").custom(value => { 43 const params = { 44 TableName: "Timecards", 45 Key: { 46 user: value, 47 attendance: "user" 48 } 49 }; 50 return documentClient.get(params).promise().then((result) => { 51 if (!!Object.keys(result).length) { 52 throw new Error('このユーザー名は既に使用されています'); 53 } 54 return true 55 }) 56 }), 57 check("password").not().isEmpty().isAlphanumeric().isLength({ min: 4, max: 15 }) 58], 59 async (req, res) => { 60 const errors = validationResult(req); 61 if (!errors.isEmpty()) { 62 return res.status(422).json({ errors: errors.array() }); 63 } 64 65 const username = req.body.username; 66 const password = req.body.password; 67 const hashedPassword = await bcrypt.hash(password, 10); 68 69 const params = { 70 user: username, 71 password: hashedPassword, 72 attendance: "user", 73 role: "common", 74 }; 75 76 documentClient 77 .put({ 78 TableName: "Timecards", 79 Item: params, 80 }) 81 .promise() 82 .then((result) => res.json({ message: "insert seccess", "csrfToken": req.csrfToken() })) 83 .catch((e) => res.status(500).json({ errors: e })); 84}); 85 86router.delete("/delete/:name", helper.authenticateToken, helper.adminUserCheck, csrfProtection, (req, res) => { 87 const params = { 88 TableName: 'Timecards', 89 Key: { 90 user: req.params.name, 91 attendance: "user" 92 } 93 }; 94 documentClient.delete(params).promise() 95 .then((result) => res.json({ message: "delete success","csrfToken": req.csrfToken() })) 96 .catch((e) => res.status(500).json({ errors: e })); 97}) 98 99router.post("/relation/update", helper.authenticateToken, helper.adminUserCheck, csrfProtection, async (req, res) => { 100 const user = req.body.user 101 const workspots = req.body.workspots 102 try { 103 for (let workspot of workspots) { 104 if (workspot.delete === "true") { 105 let params = { 106 TableName: 'Timecards', 107 Key: { 108 user: user, 109 attendance: `relation ${workspot.name}` 110 } 111 }; 112 await documentClient.delete(params).promise(); 113 } else { 114 const result = await geocoder.geocode(workspot.name) 115 let params = { 116 user: user, 117 attendance: `relation ${workspot.name}`, 118 workspot: workspot.name, 119 latitude: result[0].latitude, 120 longitude: result[0].longitude 121 } 122 await documentClient 123 .put({ 124 TableName: "Timecards", 125 Item: params, 126 }).promise() 127 } 128 } 129 } catch (e) { 130 return res.status(500).json(e.message) 131 } 132 res.json({ "message": "insert success", "csrfToken": req.csrfToken() }) 133}) 134 135router.get("/relation/index/:username", csrfProtection, (req, res) => { 136 const username = req.params.username; 137 const params = { 138 TableName: 'Timecards', 139 ExpressionAttributeNames: { '#u':'user', '#a': 'attendance' }, 140 ExpressionAttributeValues: { ':uval':username ,':aval': "relation" }, 141 KeyConditionExpression: '#u = :uval AND begins_with(#a, :aval)' 142 } 143 documentClient.query(params, (err, result) => { 144 if (err) { 145 res.status(500).json({ errors: err }) 146 } else { 147 res.json({ "relations": result.Items, "csrfToken": req.csrfToken() }) 148 } 149 }) 150}) 151 152module.exports = router;
認証処理を実装してあるAPIがあります
authjs
1const express = require('express'); 2const router = express.Router(); 3const bcrypt = require("bcrypt"); 4const jwt = require('jsonwebtoken'); 5const documentClient = require("../../dbconnect") 6 7router.post("/login", async(req, res) => { 8 const username = req.body.username; 9 const password = req.body.password; 10 const params = { 11 TableName: "Timecards", 12 Key: { 13 user: username, 14 attendance: "user" 15 } 16 }; 17 const result = await documentClient.get(params).promise(); 18 if (!Object.keys(result).length) { 19 res.send(404).json({ "message": "request user is not found" }) 20 return; 21 } 22 const comparedPassword = await bcrypt.compare(password, result.Item.password); 23 if (!comparedPassword) { 24 res.send(401); 25 return 26 } 27 const user = { 28 name: result.Item.user, 29 role: result.Item.role, 30 }; 31 const accessToken = generateAccessToken(user); 32 const refreshToken = jwt.sign(user, process.env.REFRESH_TOKEN_SECRET, { 33 expiresIn: "90d", 34 }); 35 res.json({ accessToken: accessToken, refreshToken: refreshToken }); 36}); 37 38router.post("/token", (req, res) => { 39 const refreshToken = req.body.token; 40 if (refreshToken == null) return res.send(401); 41 jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => { 42 if (err) return res.send(403); 43 const accessToken = generateAccessToken({ 44 name: user.name, 45 role: user.role, 46 }); 47 res.json({ accessToken: accessToken }); 48 }); 49}); 50 51const generateAccessToken = (user) => { 52 return jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, { expiresIn: "1h" }); 53} 54 55module.exports = router;
疑問点
・auth.js
の認証処理にもcsrfProtection
をかけるべきか
・POST,DELETEリクエストのレスポンスにcsrf-tokenを含める必要はあるのか
・エラーレスポンスにcsrf-tokenを含める必要はあるのか
素人質問で恐縮ですがよろしくお願いします。
補足情報(FW/ツールのバージョンなど)
node --version
v16.9.1
npm list --depth 0
├── @vendia/serverless-express@4.3.11
├── aws-sdk@2.995.0
├── bcrypt@5.0.1
├── body-parser@1.19.0
├── csurf@1.11.0
├── dayjs@1.10.7
├── express-session@1.17.2
├── express-validator@6.12.2
├── express@4.17.1
├── geo-position.ts@1.4.1
├── jsonwebtoken@8.5.1
├── node-geocoder@3.27.0
├── nodemon@2.0.13
└── xlsx-populate@1.21.0
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/10/19 08:02