中堅プログラマーの備忘録

忘れっぽくなってきたので備忘録として・・・

【vue.js + node.js】APIでBasic認証を行う

1.概要

フロントエンドは【vue.js】、バックエンドは【Node.js】を使い
簡単なBasic認証を使ったSPAを開発します。

2.完成予定

下図のようなログイン画面を作成し、
f:id:tsu--kun:20200821075912p:plain

Basic認証がOKであれば、下図のような適当なページを表示するといったものになります。
f:id:tsu--kun:20200821080223p:plainf:id:tsu--kun:20200821080235p:plain

3.開発準備

まずは開発に必要な環境を構築します。
私の開発用PCはWindows10になります。

①Node.js

Node.jsについては【nodist】を使った管理をしています。
詳しくは下記の記事を参照して下さい。

www.chuken-engineer.com

今回使ったバージョンは【12.13.0】です。


②npm

npmとはNode Package Manageの略です。
これはNode.jsをインストールすると一緒にインストールされます。

今回使ったバージョンは【6.9.0】です。


③@vue/cli

vue.jsの開発ツール@vue/cliのインストールを行います。
【-g】とすることでグローバルインストールになります。

>npm install -g @vue/cli

今回使ったバージョンは【4.4.6】です。


4.プロジェクトの作成

今回は【basicsample】というフォルダの下に
【frontend】
【backend】
という名前の2つのプロジェクトを作成していきます。

フロントエンド

まずはプロジェクトを作成します。
プロジェクト名は半角英数字と小文字のみで指定します。

>cd basicsample
>vue create frontend

? Please pick a preset: (Use arrow keys)
> default (babel, eslint)
  Manually select features 

default (babel, eslint)を選択し【ENTER】を押します。

下記のように表示されたら完了になります。

・ ・・ Successfully created project frontend.
・ ・・ Get started with the following commands:

 $ cd frontend
 $ npm run serve


次にプロジェクトフォルダに移動して必要なパッケージを追加していきます。

>cd frontend
①vuetifyのインストール

Vue.jsのマテリアルデザインコンポーネントフレームワークです。
簡単に奇麗で直観的なUIを作成することが可能になります。

>vue add vuetify
? Choose a preset: (Use arrow keys)
> Default (recommended)
  Prototype (rapid development)
  Configure (advanced)

Default (recommended)を選択し【ENTER】を押します。

今回はログイン画面を簡単に作成する為に使います。


②vue-routerのインストール

SPAでルーティング制御をするためのものになります。

>npm install vue-router

今回はコンポーネントとルートのマッピングはもちろんのこと、ナビゲーションガードにも使います。


③axiosのインストール

ブラウザとnode.js上で動くPromiseベースのHTTPクライアントです。
jQueryでいうajaxと同じようなものです。
Vue.jsでは非同期通信を行うのにaxiosを使うのがスタンダードとなっています。

>npm install axios

今回は認証情報をバックエンド側にPOSTする為に使います。


④vuexの追加

Vue.js アプリケーションのための 状態管理パターン + ライブラリです。
簡単に言うと異なるコンポーネントで情報を共有するために使います。

>npm install vuex

今回は認証情報をコンポーネント間で共有する為に使います。


追加したパッケージたちは【pachage.json】の【dependencies】に
しっかりと記載されています。

"dependencies": {
  "axios": "^0.19.2",
  "core-js": "^3.6.5",
  "vue": "^2.6.11",
  "vue-router": "^3.4.3",
  "vuetify": "^2.3.9",
  "vuex": "^3.5.1"
},

また、--saveオプションは【npm v5】以降不要になっているのでここでは省いています。

バックエンド

まずはプロジェクトを作成します。

>cd basicsample
>mkdir backend
>cd backend
>npm init -y

npm initで初期化処理を行うと、アプリケーションに必要な情報を対話形式で入力していくことになります。
ここでの情報は【package.json】に記録されます。
今回は特に意識する必要がないので【-y】でとばしています。

次に必要なパッケージを追加していきます。


①expressのインストール

Node.js のための高速で、革新的な、最小限のWebフレームワーク。
これはNode.jsでWebアプリケーションを開発するためには必須といっていいでしょう。

>npm install express
②body-parserのインストール

HTML フォームからポストされたデータをパースしてくれます。

>npm install body-parser

今回はreq.body経由でデータを取得するために使います。


③corsのインストール

Cross-Origin Resource Sharing オリジン間リソース共有のこと。
ブラウザは異なるオリジン間のアクセスに制限をかけています。
別のオリジンサーバーへのアクセスを許可出来るようになります。

>npm install cors


これでとりあえず準備は完了です。


5.コード

それでは実際にコードを書いていきます。

フロントエンド

①src/main.js

アプリケーションのエントリポイントです。
まずは使用するモジュールをimportしていきます。
Vue.jsのプラグインであるaxiosをVue.use()メソッドで使えるようにします。
更にPOST先のbackend側のURLを設定しています。

import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';
import router from './router.js'
import axios from 'axios'
import store from '@/store'

Vue.config.productionTip = false

Vue.use({
  install (Vue) {
    Vue.prototype.$axios = axios.create({
      baseURL: 'http://localhost:3000/'
    })
  }
})

new Vue({
  vuetify,
  router,
  store,
  render: h => h(App)
}).$mount('#app')
②src/router.js

ルートのマッピングになります。
認証済でなければPage1、Page2を表示出来ないようにし、
Login画面に飛ばすように設定しています。

import Vue from 'vue'
import Router from "vue-router"
import Page_1 from "./components/Page1.vue"
import Page_2 from "./components/Page2.vue"
import Login from "./components/Login.vue"
import Store from '@/store/index.js'

Vue.use(Router)

//export default new Router({
const router = new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    //ルーティングの設定
    {
      path: '/page1',           //ブラウザに表示されるURL
      component: Page_1,        //表示するコンポーネント
      name: 'page1',            //ルートの名前を指定
      meta: {
        requiresAuth: true
      }
    },
    {
      path: '/Page2',            
      component: Page_2,
      name: 'page2',
      meta: {
        requiresAuth: true
      }
    },
    {
      path: '/',            
      component: Login,
      name: 'home',
    },
    {
      path: '/Login',            
      component: Login,
      name: 'login',
    }
  ]
});

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!Store.state.isLogin) {
      next({
        path: '/Login',
        query: {
          redirect: to.fullPath
        }
      })
    } else {
      next();
    }
  } else {
    next(); 
  }
});

export default router
③src/store/index.js

アプリケーションの状態管理に使います。
isLoginで認証済かどうかを判断しています。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        isLogin : false,
        userId : ''
    },
    mutations: {
        auth(state, user) {
            state.isLogin = true;
            state.userId = user;
        }
    },
    actions: {
        fetch(context, user) {
            context.commit('auth', user);
        }
    },
    modules: {},
})

export default store
④src/components/Login.vue

ログイン画面になります。

<template>
  <v-app>
    <v-card width="500px" class="mx-auto mt-5">
      <v-toolbar color="primary" flat>
        test
      </v-toolbar>
      <v-card-title>
        Login?
      </v-card-title>

      <v-card-text>
        <v-form>
          <v-text-field prepend-icon="mdi-account-circle" label="user ID" v-model="authId"/>
          <v-text-field v-bind:type="showPassword ? 'text' : 'password'" prepend-icon="mdi-lock" 
                        v-bind:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'" 
                        label="password" @click:append="showPassword = !showPassword" v-model="authPass"/>
          <v-card-actions>
            <v-btn block class="info" @click='post'>LogIn</v-btn>
          </v-card-actions>

          <div class="mt-12 text-center">
            {{ msg }}
          </div>

        </v-form>
      </v-card-text>
    </v-card>
  </v-app>
</template>

<script>
export default {
  name: 'Login',
  data () {
    return {
      showPassword : false,
      msg : 'userIDとpasswordを入力して下さい',
      authId : '',
      authPass : ''
    }
  },
  methods: {
    async post() {
      const data = { id : this.authId, pass : this.authPass };
      this.msg = await this.$axios.post('/test', data)
      .then(function (response) {
        console.log(response);
        return response.data.message;
      })
      .catch(function (error) {
        console.log(error);
        return error.message;
      });
      
      if(this.msg == 'OK'){
        this.$store.dispatch("fetch", this.authId);
        this.$router.push('/Page1');
      }
    }
  }
};
</script>
⑤src/components/Page1.vue

<template>
  <div>
      <h1>ここはページ1です</h1>
      <h3>user:{{$store.state.userId}}</h3>
      <router-link to="/page2">ページ2へ</router-link>
  </div>
</template>
⑥src/components/Page2.vue

<template>
  <div>
      <h1>ここはページ2です</h1>
      <h3>user:{{$store.state.userId}}</h3>
      <router-link to="/page1">ページ1へ</router-link>
  </div>
</template>

バックエンド

①index.js

バックエンドはこれだけです。
id、passwordがtestであればOKを返します。
本来であればDBなどから取得した方がいいのですが
今回はベタ書きしています。

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')

const app = express()
app.use(bodyParser.json())
app.use(cors())

app.post('/test', function(req, res) {
  if(req.body.id == 'test' && req.body.pass == 'test'){
    res.send({
      message: 'OK'
    })
  }else{
    res.send({
      message: '認証エラー'
    })
  }
 
})

app.listen(process.env.PORT || 3000)

6.動作確認

フロントエンド側は

>npm run serve


バックエンド側は

>node index.js

で起動させます。

ブラウザで【http://localhost:8080/】にアクセスすると
【Login.vue】で作成したログイン画面が表示されます。

user ID :test
password:test

と入力すると
【Page1.vue】が表示されます。
ログインが成功していない状態で直接
http://localhost:8080/Page1
を表示しようとするとログイン画面に飛ばされます。