#はじめに
この記事ではバックエンドにRailsのAPIモード、フロントエンドにVue.jsを使ったSPAにdevise_token_authを用いて、ユーザー登録とログイン機能を実装していきます。
本記事ではdevise_token_authの導入方法などの説明は省いていますので、以下の記事を参照してください。
- Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(Rails編)
- [Rails] devise token auth を使う
- deviseのルーティングをカスタマイズする
- 【翻訳】devise-auth-token公式ドキュメント
#環境
- 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という名前です。
ルーティングは少しカスタマイズしています。
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も受け取れるようにします。
class Auth::RegistrationsController < DeviseTokenAuth::RegistrationsController
private
def sign_up_params
params.permit(:name, :email, :password)
end
end
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にアクセスできるようにします。
import axios from 'axios'
export default axios.create({
baseURL: process.env.VUE_APP_API_BASE
})
frontendの直下に.env.developmentファイルを作成して、環境変数を定義します。
また、このファイルを.gitignoreに追加します。
VUE_APP_API_BASE=http://localhost:3000
# 追加
.env.development
#フロントエンドのルーティング
$ npm install vue-router
srcディレクトリにrouter.jsファイルを作ります。
今回はユーザー登録とログイン機能のみなのでHome、SignUp、LogInの3つのコンポーネントを作成します。
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を追加します。
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は後ほど実装します。
<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つのファイルを作ります。
<template>
<div>
<h1>HOME</h1>
</div>
</template>
<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>
<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を追加します。
....
import store from './store'
....
new Vue({
store,
....
srcディレクトリにstore.jsを作ります。
- トークンをstateのauthDataで保持
- gettersで現在保持しているトークンを参照
- mutationsで取得したトークンをstateに反映(同期処理)
- actionsでAPIにアクセス(非同期処理)
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を実行します。
....
<script>
export default {
...
methods: {
signUp () {
this.$store.dispatch('signup', {
name: this.name,
email: this.email,
password: this.password
})
}
}
}
</script>
....
<script>
export default {
...
methods: {
logIn () {
this.$store.dispatch('login', {
email: this.email,
password: this.password
})
}
}
}
</script>
最後に
- ログインしていない場合はSign UpとLog Inを、ログインしている場合はLog Outを表示させる機能
- ログアウト機能
を完成させます。
....
<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を再起動すると解消されます。