【Node.js】axiosで CSRF 対策への対応
どうも,筆者です.
今回は,axios を用いて,外部サーバと通信する際の CSRF 対策への対応方法を示す。
ここでは,Django で構築された外部サーバ http://hogehoge.com/login
にログイン後,同一サーバの http://hogehoge.com/loggedin/sample
にアクセスすることを考える.前提として、対象の URL はログイン済みの時のみアクセス可能とする.
python の場合
普段,python を利用しているので,requests
モジュールによる実装を示しておく.今回は,ここに示す内容を Node.js で実現する.
準備
pip install requests
実装
import requests base_url = 'http://hogehoge.com' with requests.session() as session: # step1: login login_url = '{}/login'.format(base_url) session.get(login_url) csrf_token = session.cookies['csrftoken'] data = { 'csrfmiddlewaretoken': csrf_token, 'username': 'username', 'password': 'password', } session.post(login_url, data=data, headers=dict(Referer=login_url)) # Step2: access to loggedin/sample response = session.get('{}/loggedin/sample'.format(base_url)) print(response.text) # loggedin/sampleのページの内容が表示される
Nodejs
Node.js による実装を示す.
準備
npm install axios npm install axios-cookiejar-support npm install tough-cookie npm install querystring
実装
const querystring = require('querystring'); const axios = require('axios'); const axiosCookieJarSupport = require('axios-cookiejar-support').default; const tough = require('tough-cookie'); axiosCookieJarSupport(axios); class CustomAxios { constructor(baseURL) { const Cookie = tough.Cookie; const cookieJar = new tough.CookieJar(); const csrfHeaderName = 'X-CSRFToken'; // 外部サーバの設定に合わせる const csrfCookieName= 'csrftoken'; // 外部サーバの設定に合わせる this.postTokenName = 'csrfmiddlewaretoken'; // 外部サーバの設定に合わせる this.csrfToken = null; this.api = axios.create({ baseURL: baseURL, jar: cookieJar, withCredentials: true, xsrfHeaderName: csrfHeaderName, xsrfCookieName: csrfCookieName, headers: { 'x-requested-with': 'XMLHttpRequest', }, }); // Add cookie support for Node this.api.interceptors.request.use((config) => { if (this.csrfToken) { config.headers[csrfHeaderName.toLowerCase()] = this.csrfToken; } cookieJar.getCookies(config.baseURL, (err, cookies) => { if (!err && cookies) { config.headers['cookie'] = cookies.join('; '); } }); return config; }); this.api.interceptors.response.use((response) => { const header = response.headers['set-cookie']; if (header) { const cookies = (header instanceof Array) ? header.map(Cookie.parse) : [Cookie.parse(header)]; cookies.forEach((cookie) => { cookieJar.setCookie(cookie, response.config.baseURL, (err, targetCookie) => { if (!err && targetCookie) { // Store previous csrfToken if (targetCookie.key === csrfCookieName) { this.csrfToken = targetCookie.value; } } }); }); } return response; }); // bind this.get = this.get.bind(this); this.post = this.post.bind(this); } async get(linkName, params) { const data = params || {}; // get request const response = await this.api.get(linkName, {params: data}); return response; } async post(linkName, sendData) { let response; // get csrfToken response = await this.api.get(linkName); const data = Object.assign({[this.postTokenName]: this.csrfToken}, JSON.parse(JSON.stringify(sendData))); const headers = { Referer: `${response.config.baseURL}${linkName}`, }; // post request response = await this.api.post(linkName, querystring.stringify(data), {headers: headers}); return response; } }
実行例
(async () => { const session = new CustomAxios('http://hogehoge.com'); let response; // Step1: login const data = { username: 'username', password: 'password', }; response = await session.post('/login', data); // Step2: access to loggedin/sample response = await session.get('/loggedin/sample'); console.log(response.data); })().catch((err) => console.log(err));