2
Help us understand the problem. What are the problem?

posted at

updated at

Organization

Vue3+Node.jsで認証機能を実装する

はじめに

サーバー側、RPA開発の経験が主なのですが、フロント側の経験も積みたく、勉強も兼ねてVue.jsを使った社内システムの開発を最近はじめました。
ほぼ初めて触るのでせっかくならば最新バージョンでと思いVue3で実装をしてみましたので、ここに備忘録として作業内容を残します。

// 余談
現時点(2022/04/09)ではナレッジが少ないことと対応ライブラリが少ない事から、今後の開発が辛いことが分かりましたので、社内システムはVue2で作り直そうと思っています。

本記事で紹介すること

  1. 構成図
  2. 完成イメージ
  3. VueCLIからVueプロジェクト作成
  4. フロントエンド-認証画面の実装
  5. Node.jsプロジェクト作成
  6. バックエンド-認証処理の実装

※Node.jsとVue.jsのインストールは割愛しております。

構成図

ざっくりこんな感じです。
構成図.png

// コメント
今回はやっておりませんが、実際に作り上げる時はNode.jsとデータベースを連携させることになると思います。
その際はNode.jsプロジェクトにデータベースのモジュールを入れてあげれば出来るようです。

完成イメージ

ログイン画面
ログイン画面.png
認証後に遷移するPage1画面
Page1.png
Page2画面
Page2.png

フロントエンド側

1. VueCLIからVueプロジェクト作成

参考サイト:https://webrandum.net/vue-cli-gui/

プロジェクトの生成
まず、ターミナルで開発するフォルダに移動し、VueCLI起動コマンドを実行します。
※プロジェクト作成時のデフォルトがVueCLIを起動したカレントパスになるため。
※作成時に作成先を指定することもできます。

$ cd {開発フォルダパス}
$ vue ui

起動メッセージが表示された後、自動的にブラウザが起動しVueのGUI画面が表示されます。

次にVueプロジェクトマネージャ画面から作成ここに新しいプロジェクトを作成するをクリックしてください。
プロジェクト作成1.png

プロジェクトの設定
プロジェクト設定画面が表示されるため、順番に設定していきます。
今回は以下の様に設定しました。

項目名 入力値
プロジェクトフォルダ {フロント側プロジェクトと分かるような名前}
パッケージマネージャ デフォルト
追加オプション 存在する場合、対象フォルダを上書きする:OFF
初心者向けインストラクションなしでプロジェクトを構築:OFF
Git初期化 OFF
プリセット デフォルトプリセットVue3

プロジェクトの設定ができたらプロジェクトを作成するをクリックしてプロジェクトを生成します。
完了するとプロジェクトのダッシュボード画面に移動します。

プロジェクトの確認
作成したVueプロジェクトのダッシュボード画面からタスクメニューをクリックし、タスクの実行ボタンをクリック。
起動処理完了後、アプリを開くボタンをクリックしてサンプル画面が表示されることを確認します。
プロジェクト確認.png

// コメント
起動に失敗した場合、出力ボタンをクリックするとエラー内容を確認することができます。

// コメント
プロジェクト作成以降はVueCLIを毎回起動せずにコマンドで起動した方が楽だと思います。
その際は以下の通りコマンドになります。
cd {作成したフロントエンド側プロジェクトのパス}
npm run serve
起動後、http://localhost:8080にアクセス。

プラグインの設定
プラグインメニューに移動し、プラグインをインストールします。
今回は以下を追加しました。

プラグイン 機能
VueRouter ルーティング制御。画面遷移を制御できる。
Vuex すべてのコンポーネントでデータを一元管理するための仕組み
ESlint JavaScript のための静的検証ツール

依存関係の設定
依存メニューに移動し、依存をインストールします。

依存 機能
axios HTTPクライアント。REST-APIの実装を容易に行えるようになる。
Bootstrap CSSフレームワーク。Vueに最適化されたBootstrapVueもありますがまだVue3に未対応のため今回はこちらを使用。

2. 認証画面の実装

参考サイト1:https://qiita.com/tky_st/items/03faba81129e4877c3ea
参考サイト2:https://www.chuken-engineer.com/entry/2020/08/21/163721
参考サイト3:https://v3.ja.vuejs.org/guide/migration/global-api.html#新しいグローバル-api-createapp

以下、ファイルを追加・編集していきます。

ファイル 説明
src/main.js アプリの定義
src/store/index.js アプリ全体の状態を管理するための定義
src/router/index.js 画面遷移を制御
src/components/Login.vue 認証画面の画面定義
src/components/Page1.vue 認証成功時に最初に遷移する画面の定義
src/components/Page2.vue 認証成功時のPage1から遷移出来る画面の定義

src/main.js
まず、vueの定義をします。
Vue2系とVue3系では記述の仕方が変更されたようで、axiosの定義方法を理解するのに少し手こずりました。

src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import axios from 'axios'
import store from './store'
import router from './router'
import 'bootstrap/dist/css/bootstrap.min.css'

const app = createApp(App)

// axiosを使用できるように定義
app.config.globalProperties.$axios = axios.create({
  baseURL: 'http://localhost:3000/'
})

app.use(router)
app.use(store)
app.mount('#app')

src/store/index.js
認証状態を管理するための定義をします。

src/store/index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
    isLogin: false,
    userId: ''
  },
  mutations: {
    auth(state, user){
      state.isLogin = true;
      state.userId = user;
    }
  },
  actions: {
    fetch(context, user){
      context.commit('auth', user);
    }
  },
  modules: {
  }
})

src/router/index.js
VueRouterを使って画面遷移の定義をします。

src/router/index.js
import { createRouter, createWebHashHistory } 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'

const routes = [
  {
    path: '/page1',
    name: 'page1',
    component: Page_1,
    meta: {
      requiresAuth: true   // 認証済の時のみ閲覧可能となるように定義
    }
  },
  {
    path: '/page2',
    name: 'page2',
    component: Page_2,
    meta: {
      requiresAuth: true   // 認証済の時のみ閲覧可能となるように定義
    }
  },
  {
    path: '/',
    name: 'home',
    component: Login
  },
  {
    path: '/Login',
    name: 'login',
    component: Login
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  base: process.env.BASE_URL,
  routes
})

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/components/Login.vue
src/componentsフォルダの下にログイン画面を作ります。

src/components/Login.vue
<template>
  <div class="login">
    <h2>Sign in</h2>
    <div class="container" style="width:500px">
      <div class="row align-items-center">
        <div class="col-md-4">アカウント名</div>
        <div class="col-md-3">
          <input v-model="authId" type="text" placeholder="Username">
        </div>
      </div>
      <div class="row align-items-center">
        <div class="col-md-4">パスワード</div>
        <div class="col-md-3">
          <input v-model="authPass" type="password" placeholder="Password">
        </div>
      </div>
      <div class="row align-items-center">
        <div class="col-md-12">
          {{ msg }}
        </div>
      </div>
      <div class="row align-items-center">
        <div class="col-md-12">
          <button class="btn btn-info btn-block login" v-on:click="post">ログイン</button>
        </div>
      </div>
    </div>
  </div>
</template>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.login {
  margin-top: 20px;

  display: flex;
  flex-flow: column nowrap;
  justify-content: center;
  align-items: center;
}
input {
  margin: 10px 0;
  padding: 10px;
}
</style>
<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('/fnclogin', data)    // バックエンド側にPOST
      .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
src/componentsフォルダの下に認証後に遷移するページを作ります。

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
src/componentsフォルダの下にPage1画面から遷移するページを作ります。

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

バックエンド側

1. Node.jsプロジェクト作成

ExpressGeneratorをインストール
Node.jsのWEBフレームワークをインストールします。

$ npm install -g express-generator

プロジェクトの作成
ExpressGeneratorを使ってバックエンド側のプロジェクトを作成します。

$ cd {開発フォルダパス}
$ express -e {バックエンド側プロジェクトと分かるような名前}

Node.jsパッケージをインストール
作成したプロジェクトにNode.jsのパッケージをインストールします。

$ cd {作成したバックエンド側プロジェクトのパス}
$ npm install

起動確認
バックエンドプロジェクトを起動。
http://localhost:3000にアクセスしてサンプル画面が表示されるか確認します。

$ npm start

corsのインストール
フロントエンドとバックエンドでHTTP通信出来るようにするため、バックエンドプロジェクトにcorsをインストールします。

$ cd {バックエンド側プロジェクトのパス}
$ npm install cors

2. 認証処理の実装

以下、ファイルを追加・編集していきます。

ファイル 説明
app.js アプリの定義
routes/login.js 認証機能の実装

app.js
アプリ定義に認証機能のルートを定義します。

app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const cors = require('cors')

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var loginRouter = require('./routes/login');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// cors設定
app.use(cors());

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  next();
 });

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/fnclogin', loginRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

routes/login.js
routesフォルダの下に認証処理を作ります。

routes/login.js
const express = require('express');
const router = express.Router();

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

module.exports = router;

動作確認

フロントエンド側、バックエンド側をそれぞれ起動し動作を確認します。

フロントエンド側の起動
ターミナルを起動して以下コマンドを実行

$ cd {フロントエンド側プロジェクトのパス}
$ npm run serve

バックエンド側の起動
フロントエンドのターミナルとは別に新たにターミナルを起動して以下コマンドを実行

$ cd {バックエンド側プロジェクトのパス}
$ npm start

画面にアクセス
http://localhost:8080/にアクセス。
src/components/Login.vueで作ったログイン画面が表示されるので、以下アカウント情報を入力してログインします。
正しく動作すればsrc/components/Page1.vueで作ったPage1画面に遷移するはずです。

  • アカウント名:test
  • パスワード:test

所感

分からない事だらけの状態で記事を参考に作り始めたため、色々と手こずりました。
参考にさせてもらった記事がバージョン違いなどを理由に実装方法が異なっていたため、それの意図やバージョンごとの実装方法の違いを知るところからのスタートでした。
100%理解はできていませんが、大変勉強になりました。
最初に書いたとおり、社内システムの方はVue2で進めようと思うのでまたアウトプットできることがあれば記事にしたいと思います。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
2
Help us understand the problem. What are the problem?