Vue.jsとDjango-Rest-Frameworkで神経衰弱アプリを作ってみる【その6】~ユーザ認証編2~

[<< その5](https://qiita.com/Butterthon/items/b87e3cd2d68deff6ce87)

# ログインを実装
### ログイン画面作成
```Login.vue
<template>
  <v-form>
    <v-container>
      <v-row>
        <v-col cols="12">
          <v-text-field
            v-model="username"
            label="Username"
            outlined
          ></v-text-field>
        </v-col>
      </v-row>

      <v-row>
        <v-col cols="12">
          <v-text-field
            v-model="password"
            outlined
            label="Password"
            style="min-height: 96px"
            type="password"
          >
          </v-text-field>
        </v-col>
      </v-row>

      <div class="text-right">
        <v-btn depressed large color="primary">ログイン</v-btn>
      </div>
    </v-container>
  </v-form>
</template>

<script>
export default {
  name: 'Login',
  data: function () {
    return {
      username: null,
      password: null
    }
  },
  mounted () {
    //
  }
}
</script>
```

### router/index.tsを修正
```router/index.ts
import Vue from 'vue'
import Router from 'vue-router'
import PlayScreen from '@/views/PlayScreen.vue'
import Login from '@/views/Login.vue'

Vue.use(Router)

export default new Router({
  mode:'history',
  routes: [
    {
      path: '/',
      redirect: 'login'
    },
    {
      path: '/login',
      name: 'login',
      component: Login
    },
    {
      path: '/playscreen',
      name: 'playscreen',
      component: PlayScreen
    },
  ]
})

```

`npm run dev`すると作成したログイン画面が初期表示されるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/fb808b7e-3b69-6ea5-4991-ed6cda9fee7a.png)

### vuexのインストール
```
npm install vuex --save
```

### axiosをインストール
axiosは「Promise ベースの HTTP クライアント」です（[公式より](https://github.com/axios/axios)）。

```
npm install axios --save
```

### axiosでget, postリクエストを投げる共通関数を用意

`src`直下に`api`フォルダを用意し、またその直下にcommon.tsを作成してaxiosの共通関数を用意

```
concentratio # プロジェクトルートディレクトリ
├── config
│   ├── ...
│
├── frontend
│   ├── ...
│   ├── ..
│   ├── .
│   └── src
│         └──api # 作成
│             └──common.ts # 作成
└── manage.py
```

```src/api/common.ts
import axios from "axios";

const baseRequest = axios.create({
  baseURL: "http://localhost:8000",
  headers: {
      "Content-Type": "application/json",
  }
});

/**
 * GETリクエスト
 * @param url URL
 */
function get(url) {
  return baseRequest.get(url);
}

/**
 * POSTリクエスト
 * @param url URL
 */
function post(url, payload) {
  return baseRequest.post(url, payload);
}

export default {
    get,
    post
};

```

### 認証用のvuexモジュールを用意
<font color="red">
単一のgettersやactionsで管理するとソースコードが膨れ上がるだけだなあと思って機能毎にモジュール分割してみようと思って挑んだのですが、見事に半日ほどハマりました・・・。
</font>

```
concentratio # プロジェクトルートディレクトリ
├── config
│   ├── ...
│
├── frontend
│   ├── ...
│   ├── ..
│   ├── .
│   └── src
│         └──store # 作成
│             └──authenticates # 作成
│                 ├──actions.ts # 作成
│                 └──index.ts.ts # 作成
│                 └──mutations.ts # 作成
└── manage.py
```

```store/authenticates/actions.ts
import commonAxios from "../../api/common";

export default {
  LOGIN: function({ commit }, payload) {
    return new Promise((success, error) => {
      commonAxios.post("/api-token-auth/", payload).then(
        response => {
          // 成功時の処理
          commit("SET_TOKEN", response.data.token); // mutations.tsのSET_TOKEN関数にトークンを渡す
          commonAxios.baseRequest.defaults.headers.common['Authorization'] = `JWT ${response.data.token}`;
          success();
        }
      );
    });
  }
}

```
<font color="red">
上記、urlの最後に"/(スラッシュ)"を付け忘れるとCORSではじかれてエラーになります。
これにも気づくのに時間がかかって小一時間ほどハマりました・・・。
</font>
　
　

```store/authenticates/index.ts
import actions from './actions';

export const state = {};

export default {
  namespaced: true, // 必ずつける、外すとvueのmapActionsで読み込む時にエラーになるよ。
  state,
  actions,
  mutations
};

```

```store/authenticates/mutations.ts
export default {
  SET_TOKEN: function(state, token) {
    state.token = token
  }
};

```

`src/store`直下に`index.ts`を作成

```store/index.ts
import Vue from "vue";
import Vuex from "vuex";
import authModules from "./authenticates"; // authenticates配下のモジュールをauthModulesという名前で読み込む

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    authModules: authModules,
  }
});

```

`src/main.ts`を修正

```src/main.ts
.
..
...
import store from "./store"; // 追加
...
..
.
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
  vuetify: new Vuetify(),
  store // 追加
});

```

Login.vueを修正

```Login.vue
<template>
  <v-form>
    <v-container>
      ...
      ..
      .

      <div class="text-right">
        <v-btn depressed large color="primary" @click="login">
          ログイン
        </v-btn>
      </div>
    </v-container>
  </v-form>
</template>

<script>
import { mapActions } from "vuex";

export default {
  name: "Login",
  data: function() {
    return {
      username: null,
      password: null
    };
  },
  methods: {
    ...mapActions("authModules", ["LOGIN"]), // モジュール分割したaction.tsからLOGIN関数を読み込み
    login: function() {
      const data = {
        username: this.username,
        password: this.password
      };
      this.LOGIN(data).then(res => {
        console.log(res);
      });
    }
  },
  mounted() {
    //
  }
};
</script>

```

### responseが返ってくるか確認
正しいusernameとpasswordを入力後ログインボタン押下でresponseが返ってくる確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/e12017dd-5032-a854-f17a-131b2951d841.png)

返ってきました!

### 認証成功後、トップページに遷移するようにする。
`Login.vue`の`console.log(res);`を`this.$router.push("/playscreen");`に書き換えるだけ。


# 以上
無事、ログイン画面からトップページに遷移することができました。