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

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

ただいまの
回答率

87.34%

Vue.jsとDjangoを用いて非同期処理を行う方法

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,717

score 78

DjangoとVue.jsにおいて、アプリを開発する際に、フロント側をVue.js、APIサーバーとしてDjangoを使用していた場合にどのようにして、CSRF対策を行うかといった部分でつまずいてしまっています。
ユーザーがURLからトップページなどにアクセスした場合に、表示されるファイルは、「Vueファイル」であるかと思います。
実際に表示されたファイルがForm画面であり、ユーザーが値を入力後、axiosによってDjango側にPOSTリクエストを行なった場合、CSRFトークンを投げなかった場合に、認証エラーとなってしまいます。
ここで疑問なのですが、Djangoのテンプレートを使用していた場合は、{ % csrf_token %}とすることで、トークンが自動で付与されましたが、Vueファイルの場合はそのようなことが出来ません。
その為、どこかでDjangoからCSRFトークンをブラウザ側へ投げる、もしくはCSRFを突破する別の方法が必要であるかと思いますが、その方法が現状見つからない状況です。

Vue側はVueCliでプロジェクトを作成し、開発を進めている段階です。
以下は、axiosを用いてDjango側へリクエストを投げるVueファイルになります。

<template>
  <div>
    <h1>test-site</h1>
    <h5>test</h5>
    <p v-if="errors.length">
      <b>Please correct the following error(s):</b>
      <ul>
        <li v-for="(error,index) in errors" :key="index">{{ error }}</li>
      </ul>
    </p>
    <form action="" method="get" v-on:submit.prevent="submit">
      <input type="text" v-model="inputUrl" name="url" placeholder="http://example.com">
      <button type="submit">送信</button>
    </form>
  </div>
</template>
<script>
import axios from 'axios'
import Cookies from 'js-cookie'
export default {
  data() {
    return {
      errors:[],
      inputUrl:'',
      csrftoken:'',
    }
  },
  methods: {
    submit(){
      this.errors = [];
      this.csrftoken = Cookies.get('csrftoken');
      console.log('csrftoken: ' , this.csrftoken);

      if(!this.inputUrl){
        this.errors.push('URLを入力して下さい')
      }else if(!this.checkFormat(this.inputUrl)){
        this.errors.push('URLが正しくありません')
      }
      if (!this.errors.length){
        axios.post(
          '/polls/check/',
          this.inputUrl,
          {
            headers:{
              'X-CSRFToken':this.csrftoken
            }
          }
          )
        .then(response => {
          console.log(response)
        })
        .catch(error => {
          console.log(error)
        })
      }
    },
    checkFormat(url){
      var re = /^http(|s):\/\/.+/
      return re.test(url);
    }
  },
}
</script>

現状、Vue側では、this.csrftoken = Cookies.get('csrftoken');にてクッキーの取得を行っていますが、当然Django側へアクセスをしていない為、CSRFトークンは存在せずundefinedとなってしまっています。
CSRFトークンに関してはDjangoのドキュメントを読み、CSRFトークンを取得する方法は分かったのですが、送信する方法は分かりませんでした。
こちらの問題につきまして、解決策が見当たらない状況な為、ご助言頂けましたら幸いです。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • dameo

    2020/08/05 08:57

    webpackの開発サーバーをそのまま使用してるということですか?最終的にもそういう運用なのですか?

    キャンセル

  • study_111

    2020/08/05 09:04 編集

    自分はVueの経験があまりなく、良く分かっていない場合は多いのですが、最終的には開発サーバーではなくAWSでサーバーを構築し運用していこうと考えています。
    しかしながら、Vue側でVueファイルを返す形とした場合は、Django側のプログラム処理を行わない為、CSRFトークンがクッキーに付与され、返されるといったことは起こり得ないのではないかと思っているのですが、そういった訳ではないのでしょうか?

    キャンセル

  • dameo

    2020/08/05 09:26

    最終的にサーバーや構成すら決まってない段階で、CSRFとかどうしたいのでしょうか?何をしたいのでしょう?今やっているのは仕事ですか?趣味ですか?仕事であれば、ちゃんと設計してください。できなければできる人にお金を払って頼んでください。

    キャンセル

回答 2

checkベストアンサー

+1

やはりCSRFの制限がある限り、DjangoからVueアプリをサーブしなきゃいけなさそうです。

肝心なのは、Django 側に django-webpack-loader と、Vue 側に webpack-bundle-tracker です。

Vue 側に webpack-bundle-tracker をインストール:

npm install --save-dev webpack-bundle-tracker

Vue 側に、 vue.config.js で下記のように設定してください:

// vue.config.js
const BundleTracker = require("webpack-bundle-tracker");

module.exports = {
  publicPath: "http://0.0.0.0:8080/",
  outputDir: "dist",

  configureWebpack: {
    plugins: [
      new BundleTracker({ filename: './webpack-stats.json' }),
    ],
  },
};

設定値を説明します:

publicPath は相対リンクの root です。django-webpack-loader によって、ポートが8080です。
outputDir はトランスパイルの結果ファイルの置き場です。後でDjangoに知らせます。
configureWebpack に BundleTracker のプラグインを設定します。その追跡ファイルも後でDjangoに知らせます。

Django 側に、django-webpack-loader をインストール:

pip install django-webpack-loader

Django 側に、settings.py で下記のように設定してください:

// settings.py
...

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'dist'),
]

...

WEBPACK_LOADER = {
   'DEFAULT': {
       'BUNDLE_DIR_NAME': '',
       'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
   }
}

...

INSTALLED_APPS = (
    ...
    'webpack_loader',
)

...

TEMPLATES = [    
    {        
        'BACKEND': 'django.template.backends.django.DjangoTemplates',        
        'DIRS': [
            os.path.join(BASE_DIR, 'templates'),
        ],
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    }
]

設定値を説明します:

STATICFILES_DIRS は静的なファイルの置き場です。先の outputDir をここに入れます。
BUNDLE_DIR_NAME は Vue の出力場所です。dist に直接出力なので、空にします。
STATS_FILE は BundleTracker の追跡ファイルです。先の追跡ファイルをここに入れます。
INSTALLED_APPS で webpack_loader を最後に入れます。
TEMPLATES で設定するフォルダに下記のテンプレートを入れます。

// templates/application.html
{% load render_bundle from webpack_loader %}
{% render_bundle 'app' %}

そして、urls.py で下記のように設定してください:

// urls.py
from django.views.generic import TemplateView

urlpatterns = [
    path('admin/', admin.site.urls),
    path("",
        TemplateView.as_view(template_name="application.html"),
        name="app",
    ),
]

上記のように設定したら、二つのターミナルを用意して、下記二つの命令をそれぞれに実行してください:

npm run serve
python manage.py runserver

そうしたら、ブラウザーで http://127.0.0.1:8000 に訪問すれば、アプリが作動しているはずです。


参照

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/09 20:38

    「 "http://0.0.0.0:8000/" 」こちらに変更しますと、「http://localhost:8080/」にアクセスした際に、何も表示されない状態になってしまっております。

    キャンセル

  • 2020/08/09 20:48

    そうですか。やはりSPAはCSRF保護と相性が悪いですね。下記のブログシリーズを参照すればいかがでしょうか? Django REST Framework なら、APIサーバーとして特化しCSRF Tokenの代わりにAPI Token使うことになります。
    https://qiita.com/Butterthon/items/95b05c15bce419846f27

    キャンセル

  • 2020/08/10 10:01

    ご丁寧にありがとうございます。
    一度、CSRF保護の対策について、教えて頂きました「API Token」などで考えてみたいと思います。
    今回は、長々とご協力頂きまして、誠にありがとうございました。

    キャンセル

0

Postする際に、設定する

Axiosを投げる際に、下記のように設定しておけばCSRFは通るかと思います。

axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
axios.post('url')
.then()
.catch()

自作プラグインを作成してデフォルトで設定する

また、puluginsディレクトリ等に、http.jsなどの名前で自作プラグインを作成することでデフォルトでこの設定することも可能です。

// plugins/http.js
import Vue from 'vue'
import axios from 'axios'

export default {
  install: (Vue, options) => {
    const http = axios.create({
      xsrfCookieName:'csrftoken',
      xsrfHeaderName:"X-CSRFTOKEN",
    })
    Vue.prototype.$axios = http
  }
}
// main.js
import http from '@/plugins/http'
Vue.use(http)
methods: {
  post () {
    this.$axios('url')
    .then()
    .catch()
  }
}

ところどころ省略はしていますが、だいたいこんな感じでいけると思います。
お役に立てれば幸いです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/06 13:25 編集

    ご返信ありがとうございます。
    そうなりますと、やはり現状の方法ですと、一度はDjango側へアクセスをし、ブラウザへレスポンスを返さなければならないといった訳だったのですね。

    キャンセル

  • 2020/08/06 13:38

    私も最近VueとDjangoの勉強を始めたばかりなので、何とも言えませんがそういうことなのかと思います。
    ちなみに私の後学のために教えていただきたいのですが、一度リクエストを送った後はVue側のCookieにはtokenが入っているのでしょうか??

    キャンセル

  • 2020/08/06 16:01 編集

    >一度リクエストを送った後はVue側のCookieにはtokenが入っているのでしょうか??
    こちらですが、自分も良く分からないのですが、何故かChromeの方ではcsrftokenが入っているのですが、firefoxの方では、レスポンスのstatus_codeが200となっているのですが、何故かクッキーにセットされないままなのですよね...

    キャンセル

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

  • ただいまの回答率 87.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る