作業中のメモ

よく「計算機」を使って作業をする.知らなかったことを中心にまとめるつもり.

【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));