Vue.jsを使用したGoogle認証機能(OAuth 2.0) を構築する方法が日本の記事に少なかったため、覚書として公開します。
今回は認証機能編ということで、認証してTokenをsessionStorageに保存するところまで実装していきます。
本章の完成イメージ
Google oAuthの準備
oAuth同意画面の設定
Google Console Platform (以後GCP) にアクセスし、oAuthの同意画面を設定します。
GCPのプロジェクト毎に一度設定すれば良いので、すでに設定済みの場合は次に進みます。
oAuth同意画面を作成
oAuth同意画面に移動し、User Typeを選択します。
プロジェクト内部のメンバーでしか使用しないページを作成する場合は内部
、インターネットに公開し誰でもログインできるようにする場合は外部
を選択し、作成
を押下します。
アプリケーション名
を入力し、保存します。
アプリケーションのロゴ
を選択してしまうとGoogleによる承認が必要になり、使用できなくなってしまうため選択しないでください。間違えて選択して保存してしまった場合、後から取り消すことができず、承認するにはLanding pageの用意とプライバシーポリシーの用意が必要になります。
oAuthクライアントの作成
認証情報に移動し、認証情報を作成
を押下します。
OAuthクライアントID
を選択肢、oAuthクライアントIDを作成します。
アプリケーションの種類
を ウェブアプリケーション
にし、名前
、URI
を入力して作成します。URI
には、WebアプリケーションのSchema + Domainを入力します。
今回はローカルで検証するため、http://localhost:8080
を入力しています。
oAuthクライアントを作成すると、クライアントID
とクライアントシークレット
が表示されるので、メモしておきます。
Vueにログイン機能の組み込み
Vueにログイン機能を組み込んでいきます。
これから使用するベースは前章で作成したものを使用しています。
ログイン情報を保存するためのStoreを作成する
ログイン情報を保存するために、Storeを作成します。
値をセッションで保存するために、sessionStorageを指定しています。
sessionStorageを指定しない場合、画面をリロードする度に値が削除されログアウトされます。
- src/store.js
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)
export default new Vuex.Store({
plugins: [createPersistedState({
key: 'vue-gauth',
paths: ['userInfo'],
storage: window.sessionStorage
})],
state: {
userInfo: {}
},
mutations: {
setUserInfo: function (state, userInfo) {
state.userInfo = userInfo
}
},
actions: {
}
})
Google oAuthのパッケージを組み込む
Google oAuthをVueで使用できるようにするため、パッケージを組み込んでいきます。
まず、vue-google-oauth2をインストールします。
yarn add vue-google-oauth2
次に、認証を行うためにインスタンスを作成し、Vueに読み込ませます。{CLIENT_ID}
には、事前にメモしたoAuthクライアントのクライアントID
を入力します。
また、先ほど作成したStoreも呼び出せるように読み込みます。
- src/main.js
import Vue from 'vue'
import App from './App.vue'
import GAuth from 'vue-google-oauth2'
import router from './router'
import store from './store'
Vue.config.productionTip = false
Vue.use(GAuth, {
clientId: '{CLIENT_ID}', scope: 'email profile openid', prompt: 'consent', fetch_basic_profile: false
})
new Vue({
render: h => h(App),
router,
store
}).$mount('#app')
トップメニューにログイン機能を作成する
ヘッダにログインボタンとログアウトボタンを作成し、ログイン/ログアウトができるようにします。
この時、userInfo
が存在しない場合のみLoginを表示し、存在する場合はLogoutを表示するようにします。
また、NeedAuthページはログイン時にのみ使用できる機能なため、userInfo
が存在する場合のみ表示されるようにします。
- src/components/PageHeader.vue
<template>
<div>
<nav class="navbar navbar-expand-lg navbar-light bg-light p-1">
<div class="navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item d-flex align-items-center">
<router-link class="text-dark px-2" to="/">Home</router-link>
</li>
<li class="nav-item d-flex align-items-center">
<router-link v-if="Object.keys($store.state.userInfo).length" class="text-dark px-2" to="/need-auth">NeedAuth</router-link>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a v-if="!Object.keys($store.state.userInfo).length" class="text-dark px-3" href="javascript:void(0);" @click.prevent="handleSignIn">Login</a>
<a v-if="Object.keys($store.state.userInfo).length" class="text-dark px-3" href="javascript:void(0);" @click.prevent="handleSignOut">Logout</a>
</li>
</ul>
</div>
</nav>
</div>
</template>
<script>
export default {
methods: {
async handleSignIn () {
try {
const googleUser = await this.$gAuth.signIn()
if (!googleUser) {
return null
}
this.$store.commit('setUserInfo', googleUser.getAuthResponse())
} catch (error) {
console.error(error)
return null
}
},
async handleSignOut () {
try {
await this.$gAuth.signOut()
this.$store.commit('setUserInfo', {})
window.location.href = '/'
} catch (error) {
console.error(error)
}
}
}
}
</script>
ログイン必須ページをログイン時にのみ表示する
ログイン必須ページとなるNeedAuth
ページを、ログイン時にしか表示できないようにします。
今のままでもトップメニューにはログイン時にしか表示されませんが、URLを指定するとアクセスできてしまうためURLを指定してもアクセスできないようにします。
- src/router.js
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import needAuth from '@/components/NeedAuth'
import store from './store'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(Router)
Vue.use(BootstrapVue)
const router = new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/need-auth',
name: 'need-auth',
component: needAuth,
meta: { requiresAuth: true }
},
]
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (Object.keys(store.state.userInfo).length) {
next()
} else {
next({ path: '/', query: { redirect: to.fullPath }})
}
}
next()
})
export default router
おわりに
以上で、基本となる認証機能の実装は完了です。
認証時に取得したTokenを使用し、Google機能にアクセスさせたり、API側でJWTトークンの検証を行いGoogleログインしたユーザにのみ利用できる機能の提供を行うことが可能となります。
今回のサンプルはGitHubにコードを公開しているので、参考にしてみてください。