4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rails API + Vue.js のSPAにdevise_token_authを使ってユーザー登録&ログイン機能を実装する

Posted at

#はじめに
この記事ではバックエンドにRailsのAPIモード、フロントエンドにVue.jsを使ったSPAにdevise_token_authを用いて、ユーザー登録とログイン機能を実装していきます。
本記事ではdevise_token_authの導入方法などの説明は省いていますので、以下の記事を参照してください。

#環境

  • Ruby 3.0.0
  • Rails 6.1.3
  • Vue.js 2.6.12
  • Vue CLI 4.5.1

#Rails側の設定
APIモードでrailsアプリケーションを作成してdevise、devise_token_auth、rack-corsを導入してあります。
プロジェクトはspaという名前です。
ルーティングは少しカスタマイズしています。

spa/config/routes.rb
Rails.application.routes.draw do
  mount_devise_token_auth_for 'User', at: 'auth', skip: [:registrations], controllers: {
    sessions: 'auth/sessions'
  }
  devise_scope :user do
    post '/auth/sign_up', to: 'auth/registrations#create', as: :user_registration
    patch '/auth/account/edit', to: 'auth/registrations#update', as: :edit_user_registration
    delete '/auth/account/delete', to: 'auth/registrations#destroy', as: :destroy_user_registration
  end
end
$ rails routes
                    ..... ...    .....                          .....
             user_session POST   /auth/sign_in(.:format)        auth/sessions#create
     destroy_user_session DELETE /auth/sign_out(.:format)       auth/sessions#destroy
                    ..... ...    .....                          .....
        user_registration POST  /auth/sign_up(.:format)        auth/registrations#create
   edit_user_registration PATCH  /auth/account/edit(.:format)    auth/registrations#update
destroy_user_registration DELETE /auth/account/delete(.:format)  auth/registrations#destroy
                    ..... ...    .....                           .....

ルーティングで指定したコントローラを作成して、デフォルトのDeviseTokenAuth::RegistrationsControllerとDeviseTokenAuth::SessionsControllerをそれぞれ継承します。
ユーザー登録にはデフォルトの:email、:password以外に:nameも受け取れるようにします。

spa/app/controllers/auth/registrations_controller.rb
class Auth::RegistrationsController < DeviseTokenAuth::RegistrationsController
  private
  
  def sign_up_params
    params.permit(:name, :email, :password)
  end
end
spa/app/controllers/auth/sessions_controller.rb
class Auth::SessionsController < DeviseTokenAuth::SessionsController
end

#フロントエンドにVue.jsを導入
Vue CLIがインストールされている前提で始めます。
フロントエンドのプロジェクト名はfrontendで、railsアプリケーション(spa)の直下に作ります。

# 現在spaにいない場合はspaに移動します。
$ cd spa

$ vue create frontend

$ cd frontend

APIにアクセスするためにaxiosをインストールします。

$ npm install axios

srcディレクトリにaxios.jsファイルを作成します。環境変数を取得してAPIにアクセスできるようにします。

spa/frontend/src/axios.js
import axios from 'axios'

export default axios.create({
  baseURL: process.env.VUE_APP_API_BASE
})

frontendの直下に.env.developmentファイルを作成して、環境変数を定義します。
また、このファイルを.gitignoreに追加します。

spa/frontend/.env.development
VUE_APP_API_BASE=http://localhost:3000
spa/frontend/.gitignore
# 追加
.env.development

#フロントエンドのルーティング

$ npm install vue-router

srcディレクトリにrouter.jsファイルを作ります。
今回はユーザー登録とログイン機能のみなのでHome、SignUp、LogInの3つのコンポーネントを作成します。

spa/frontend/src/router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home'
import SignUp from './views/SignUp'
import LogIn from './views/LogIn'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/signup',
      name: 'SignUp',
      component: SignUp
    },
    {
      path: '/login',
      name: 'Login',
      component: LogIn
    }
  ]
})

main.jsにrouterを追加します。

spa/src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

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

#表示するページを作成
computedやmethodsは後ほど実装します。

spa/frontend/src/App.vue
<template>
  <div id="app">
    <header>
      <router-link to="/">Home</router-link>
      <template v-if="!isAuthenticated">
        <router-link to="/signup">Sign Up</router-link>
        <router-link to="/login">Log In</router-link>
      </template>
      <template v-else>
        <span  class="logout" @click="logOut">Log Out</span>
      </template>
    </header>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  computed: {
    isAuthenticated () { /*ログインしていたらtrueを返す*/ }
  },
  methods: {
    logOut () {}
  }
}
</script>

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

.logout {
  cursor: pointer;
}
</style>

srcディレクトリにviewsディレクトリを作り、Home.vue、SignUp.vue、LogIn.vueの3つのファイルを作ります。

spa/frontend/src/views/Home.vue
<template>
  <div>
    <h1>HOME</h1>
  </div>
</template>
spa/frontend/src/views/SignUp.vue
<template>
  <div class="signup">
    <h1>Sign Up</h1>
    <div class="form">
      <label for="name">Name</label>
      <input type="text" v-model="name" id="name">
    </div>
    <div class="form">
      <label for="email">Email</label>
      <input type="email" v-model="email" id="email">
    </div>
    <div class="form">
      <label for="password">Password</label>
      <input type="password" v-model="password" id="password">
    </div>
    <button @click="signUp">Sign Up</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      name: '',
      email: '',
      password: ''
    }
  },
  methods: {
    signUp () {}
  }
}
</script>
spa/frontend/src/views/LogIn.vue
<template>
  <div class="login">
    <h1>Log In</h1>
    <div class="form">
      <label for="email">Email</label>
      <input type="email" v-model="email" id="email">
    </div>
    <div class="form">
      <label for="password">Password</label>
      <input type="password" v-model="password" id="password">
    </div>
    <button @click="logIn">Log In</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      email: '',
      password: ''
    }
  },
  methods: {
    logIn () {}
  }
}
</script>

#フロントエンドにユーザー登録とログイン機能を実装
ユーザー登録やログインの際に取得するトークンを保持して、複数のコンポーネントで使いたいのでvuexをインストールします。

$ npm install vuex

main.jsにstoreを追加します。

spa/frontend/src/main.js
....
import store from './store'
....
new Vue({
  store,
  ....

srcディレクトリにstore.jsを作ります。

  • トークンをstateのauthDataで保持
  • gettersで現在保持しているトークンを参照
  • mutationsで取得したトークンをstateに反映(同期処理)
  • actionsでAPIにアクセス(非同期処理)
spa/frontend/src/store.js
import Vue from 'vue'
import Vuex from 'vuex'
import router from './router'
import axios from './axios'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    authData: null
  },
  getters: {
    authData: state => state.authData
  },
  mutations: {
    updateAuthData (state, responseData) {
      state.authData = responseData
    }
  },
  actions: {
    signup ({commit}, authParams) {
      axios.post('/auth/sign_up', {
        name: authParams.name,
        email: authParams.email,
        password: authParams.password
      })
        .then(response => {
          commit('updateAuthData', response.headers)
          router.replace('/')
        })
    },
    login ({commit}, authParams) {
      axios.post('/auth/sign_in', {
        email: authParams.email,
        password: authParams.password
      })
        .then(response => {
          commit('updateAuthData', response.headers)
          router.replace('/')
        })
    },
    logout ({commit}, authParams) {
      axios.delete('/auth/sign_out', {
        headers: authParams
      })
      commit('updateAuthData', null)
      router.replace('/', () => {})
    }
  }
})

各コンポーネントで、APIにアクセスするときに必要なパラメータを取得して各actionsを実行します。

spa/frontend/src/views/SignUp.vue
....
<script>
export default {
  ...
  methods: {
    signUp () {
      this.$store.dispatch('signup', {
        name: this.name,
        email: this.email,
        password: this.password
      })
    }
  }
}
</script>
spa/frontend/src/views/LogIn.vue
....
<script>
export default {
  ...
  methods: {
    logIn () {
      this.$store.dispatch('login', {
        email: this.email,
        password: this.password
      })
    }
  }
}
</script>

最後に

  • ログインしていない場合はSign UpとLog Inを、ログインしている場合はLog Outを表示させる機能
  • ログアウト機能

を完成させます。

spa/frontend/src/App.vue
....
<script>
export default {
  name: 'App',
  computed: {
    isAuthenticated () {
      return this.$store.getters.authData !== null
    }
  },
  methods: {
    logOut () {
      this.$store.dispatch('logout', this.$store.getters.authData)
    }
  }
}
</script>
....
````

#動作チェック
アプリケーションを起動させます。
ターミナルを2つ開いて、片方はspaに移動して`$ rails s`を、もう片方はspa/frontendに移動して`$ npm run serve`を入力します。
http\://localhost:8080/にアクセスします。

現状でSign UpやLog Inは可能ですが、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1171931/cd56e441-72be-0ad6-cf5e-7a1d4b0431a8.png)
のように、"registration"や"session"というパラメータが送られてUnpermitted parameter:が表示されると思います。
これはrailsのデフォルトの挙動のようです。
[paramsとwrap_parameters](https://qiita.com/kazutosato/items/fbaa2fc0443611c627fc)
この挙動をやめたい場合は上のリンクにあるように

````ruby:spa/config/initializers/wrap_parameters.rb
ActiveSupport.on_load(:action_controller) do
  wrap_parameters format: []    # [:json]から[]に変更
end
````
として、railsを再起動すると解消されます。
4
4
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
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?