LoginSignup
3
2

More than 3 years have passed since last update.

DockerをつかってVueとRailsの開発環境にユーザー認証機能を実装する

Last updated at Posted at 2020-05-05

GWの記事3投目です。

一連の記事

  1. DockerをつかってVueとRailsの開発環境をつくる
  2. DockerをつかってVueとRailsの開発環境にユーザー認証機能を実装するこの記事!!
  3. DockerをつかったVueとRailsの環境にAPIを実装する

本記事の目的

DockerをつかってVueとRailsの開発環境をつくるの続編です。
前回環境を作ったところに、以下の機能を追加する
- ユーザーの新規登録
- ログイン機能
- Tokenの発行

手順

サーバー側

CORSの設定

cors設定に必要なものとついでにhas_secure_passwordを使用するためのものをgemfileに記載

server/Gemfile
# Gemfile
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'#28行目あたりのこの行のコメントアウトを外す
gem 'bcrypt', '~> 3.1.7' #追記

外したら保存してdocker-compose run rails bundle installを実行する

server/config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:8080'#Dockerで立ててるWebコンテナ(Vue)のアドレス

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

Userモデルの作成

Userモデルを作成します

bash
docker-compose run rails rails g model User email:string name:string password_digest:string token:string

# 終わったら
docker-compose run rails rails db:migrate

認証処理を実装する

server/app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  include ActionController::HttpAuthentication::Token::ControllerMethods

  before_action :authenticate!

  private

  def authenticate!
    authenticate_or_request_with_http_token do |token, options|
      User.find_by(token: token).present?
    end
  end

  def current_user
    @current_user ||= User.find_by(token: request.headers['Authorization'].split[1])
  end
end

before_action :authenticate!を使うことで全コントローラーのアクションに認証を掛けられる
認証なしでもAPIを使いたい場合はskip_before_actionを使う

UsersControllerの実装

bash
docker-compose run rails rails g controller Users create sign_in

ルーティングの調整を以下のようにする

server/config/routes.rb
Rails.application.routes.draw do
  resources :users, only: [ :create ] do
    collection do
      post 'sign_in'
    end
  end
end

ログイン機能と新規登録機能を実装

class UsersController < ApplicationController
  skip_before_action :authenticate!, only: [ :create, :sign_in ]

  # 新規登録 POST/user
  def create
    @user = User.new(email: params[:email], password: params[:password], name: params[:name])

    if @user.save
      render json: @user
    else
      render json: { errors: @user.errors.full_messages }, status: 400
    end
  end

  # ログイン POST/sign_in
  def sign_in
    @user = User.find_by(email: params[:email])

    if @user && @user.authenticate(params[:password])
      render json: @user
    else
      render json: { errors: ['ログインに失敗しました'] }, status: 401
    end
  end
end

これでサーバー側の準備は終了です。

フロント側

axiosの準備とElement-uiの導入

HTTP通信を行うためにAxiosを導入します

bash
docker-compose run web npm install --save axios vue-axios element-ui

終わったら、Vueで読み込むための設定をします。

front/src/main.js
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'// 追記
import 'element-ui/lib/theme-chalk/index.css'// 追記
import axios from 'axios'// 追記
import VueAxios from 'vue-axios'// 追記

Vue.use(VueAxios, axios)// 追記
Vue.use(ElementUI)// 追記

Vue.config.productionTip = false

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

Headerの作成

アプリヘッダーを作成して表示させます。

front/src/components/appheadder.vue
<template>
  <div class="appheadder">
    <router-link to="/" class="title">プロジェクト名</router-link>
    <div class="menu">
      <ul id="nav">
        <span v-if="!$store.state.token">
          <li><router-link to="/login">ログイン</router-link></li>
          <li><router-link to="/signup">新規登録</router-link></li>
        </span>
        <span v-else>
          <li><router-link to="/login">検索</router-link></li>
          <li><a @click="dologout">ログアウト</a></li>
        </span>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  name: 'appheadder',
  methods: {
    dologout () {
      this.$store.dispatch('doRegistrationToken', null)
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.appheadder{
  background-color: rgb(92, 182, 153);
  height: 50px;
}
.menu{
  text-align: right;
  float: right;
  position:  fixed;
  right: 0;
  top: 0;
}
.title{
  font-size: 40px;
  text-align: center;
  margin: auto;
  text-decoration: none;
  color: rgb(0, 0, 0);
  font-weight: bold;
}
#nav {
  padding: 0 0 0 0;
  list-style: none;
  overflow: hidden;
  display: inline;
}

#nav li {
  width: 12vw;
  text-align: center;
  background-color: #333;
  float: left;
  height: 50px;
  line-height: 50px;
  margin-right: 2px;
}

#nav li a {
  text-decoration: none;
  color: #fff;
  font-weight: bold;
}

</style>
front/src/App.vue
<template>
  <div id="app">
    <Appheadder/>
    <router-view/>
  </div>
</template>

<script>
import Appheadder from './components/appheadder.vue'
export default {
  name: 'Home',
  components: {
    Appheadder
  }
}
</script>

<style>
*{
margin: 0;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>
front/src/views/home.vue
<template>
  <div class="home">
  </div>
</template>

<script>

export default {
  name: 'Home',
  components: {
  }
}
</script>

これを変更してページの状態を確認しましょう。
コンテナを立ち上げていなければ立ち上げてhttp://localhost:8080 にアクセスして、以下のようなページが表示されていればOKです。
FireShot Capture 007 - mybutler - localhost.png

ログインページと新規登録画面の作成

まず新規登録画面を作成します

front/src/views/signup.vue
<template>
  <div class="signup">
    <h1>新規登録</h1>
    <div class="loginForm">
      <table>
        <tr>
          <th>ID</th>
          <td><el-input placeholder="Please input" v-model="id"></el-input></td>
        </tr>
        <tr>
          <th>パスワード</th>
          <td><el-input placeholder="Please input password" v-model="pass" show-password></el-input></td>
        </tr>
      </table>
      <el-button type="primary" @click="dologin">新規登録</el-button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'signup',
  data () {
    return {
      id: '',
      pass: ''
    }
  },
  components: {
  },
  methods: {
    dologin () {
      this.axios.post('http://0.0.0.0:3000/users/', {
        name: this.id,
        pwd: this.pass
      },
      {
        headers: {
          'Content-Type': 'application/json'
        }
      })
        .then((response) => {
          this.$store.dispatch('doRegistrationToken', response.data.token)
        })
        .catch((e) => {
          console.log(e)
        })
    }
  }
}
</script>
<style scoped>
  .loginForm{
    width: 80%;
    padding: 10vh 5vw;
    margin: auto;
    background-size: cover;
  }
  .loginForm *{
    margin: auto;
  }
</style>

次にログイン画面も作りますがほとんど一緒です。

front/src/views/login.vue
<template>
  <div class="login">
    <h1>ログイン</h1>
    <div class="loginForm">
      <table>
        <tr>
          <th>ID</th>
          <td><el-input placeholder="Please input" v-model="id"></el-input></td>
        </tr>
        <tr>
          <th>パスワード</th>
          <td><el-input placeholder="Please input password" v-model="pass" show-password></el-input></td>
        </tr>
      </table>
      <el-button type="primary" @click="dologin">ログイン</el-button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Login',
  data () {
    return {
      id: '',
      pass: ''
    }
  },
  components: {
  },
  methods: {
    dologin () {
      this.axios.post('http://0.0.0.0:3000/users/sign_in', {
        name: this.id,
        pwd: this.pass
      },
      {
        headers: {
          'Content-Type': 'application/json'
        }
      })
        .then((response) => {
          this.$store.dispatch('doRegistrationToken', response.data)
        })
        .catch((e) => {
          console.log(e)
        })
    }
  }
}
</script>
<style scoped>
  .loginForm{
    width: 80%;
    padding: 10vh 5vw;
    margin: auto;
    background-size: cover;
  }
  .loginForm *{
    margin: auto;
  }
</style>

続いてルーティングを直していきます。
まずヘッダーを直します

front/src/components/appheadder.vue
<template>
  <div class="appheadder">
    <router-link to="/" class="title">プロジェクト名</router-link>
    <div class="menu">
      <ul id="nav">
        <span v-if="!$store.state.token">
          <li><router-link to="/login">ログイン</router-link></li><!--変更-->
          <li><router-link to="/signup">新規登録</router-link></li><!--変更-->
        </span>
        <span v-else>
          <li><router-link to="/login">検索</router-link></li><!--変更-->
<!--以下略-->
front/src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/login.vue'
import Signup from '../views/signup.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/login',
    name: 'login',
    component: Login
  },
  {
    path: '/signup',
    name: 'signup',
    component: Signup
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

ストアにトークンを保存するための準備

front/src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    token: ''
  },
  mutations: {
    RegistrationToken (state, payload) {
      state.token = payload.token
    }
  },
  actions: {
    doRegistrationToken ({ commit }, token) {
      commit('RegistrationToken', { token })
    }
  },
  modules: {
  }
})

これで確認してみましょう!
FireShot Capture 008 - mybutler - localhost.png
ここでユーザーを登録すると右上のMENUが検索とログアウトになれば成功です。
正常にユーザーが新規登録でき、Tokenが帰ってきています。
ログアウトを押してもとに戻るのも確認してください。
その後ログインから、先程登録した情報でログインできるか確認してみてください。

あとがき

今後も続いて開発していけたらこうしてまとめていこうと思いますのでよろしくおねがいします。
あ〜、GWもあと1日か〜

参考にさせていただいた記事

めちゃくちゃおせわになりました!!
ありがとうございます!
- Rails5 APIで認証付きのWebAPIを作ってみる
- Rails5 APIモードでつくるかんたんなトークンベース認証
- vue-cliでaxiosを使用する(設定から使用方法まで)

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2