質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

CSRF

クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

Express

ExpressはNode.jsのWebアプリケーションフレームワークです。 マルチページを構築するための機能セットおよびハイブリッドのWebアプリケーションを提供します。

Q&A

解決済

1回答

2321閲覧

Expressを使用したCSRF対策のベストプラクティス

fresh_fish

総合スコア20

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

CSRF

クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

Express

ExpressはNode.jsのWebアプリケーションフレームワークです。 マルチページを構築するための機能セットおよびハイブリッドのWebアプリケーションを提供します。

0グッド

1クリップ

投稿2021/10/11 04:25

前提・実現したいこと

現在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

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

回答がないようですので一般論として書きます。

auth.jsの認証処理にもcsrfProtectionをかけるべきか

認証処理はCSRF対策は必須ではありませんが、対策している実装はよく見かけます。必要でないのに対策する理由はよく分かりません。

POST,DELETEリクエストのレスポンスにcsrf-tokenを含める必要はあるのか

一般論として、POSTは必要、DELETEはプリフライトリクエストのチェックで弾かれるので不要です。しかし、クッキーによるセッション管理ではなく、Authorizationヘッダにトークンを入れる場合は、そもそもCSRF対策そのものが不要です。

エラーレスポンスにcsrf-tokenを含める必要はあるのか

これは質問の意図が分かりません。

投稿2021/10/19 07:16

ockeghem

総合スコア11701

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fresh_fish

2021/10/19 08:02

自分なりに簡単に理解したつもりでしたがCSRF対策の基礎的な知識が不足していました。 このような稚拙な質問に回答してくださってありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問