今回達成すること
今回は、ログイン済みのユーザーをログインしたままにする処理を行います。
その他、
- ログイン後のレイアウト切り替え と
- ログアウトを実装します。
ログインフラグを作成する
Nuxt.js側でのログイン判定は、
- 有効期限内である、かつ
- Vuexのユーザーが存在する場合に
ログインしていると判定します。
このフラグは、
- レイアウトの切り替え判定や
- ログインユーザーだけがリクエストできるAPI実行の判定
に使用します。
auth.jsに認証フラグを追加する
「plugins」ディレクトリの
front/plugins/auth.js
isAuthenticated...
class Authentication {
...
isAuthenticated () {
...
}
// 追加
// Vuexのユーザーを返す
get user () {
return this.store.state.current.user || {}
}
// ユーザーオブジェクトがある場合にtrueを返す
isUserPresent () {
return ('id' in this.user)
}
// 有効期限内、かつユーザーが存在する場合にtrueを返す
get loggedIn () {
return this.isAuthenticated() && this.isUserPresent()
}
// ここまで
// ログイン業務
login ({ exp, user }) {
...
}
}
...
-
get user ()
... クラスオブジェクトに、user
というプロパティを追加する。$auth.user
で呼び出しができます。この
user
プロパティが呼び出された時に、関数の中で計算した値が返されます。
ゲッターを使用し、HTML内で使用頻度が高いuser
とloggedIn
を、$auth
のプロパティとして登録しています。
ログインフラグを切り替える
$auth.loggedIn
に変更します。
front/pages/index.vue
...
export default {
// $authを追加
layout ({ $auth }) {
// 追加
return $auth.loggedIn ? 'loggedIn' : 'welcome'
// 削除
// return store.state.loggedIn ? 'loggedIn' : 'welcome'
},
...
}
コンテナを起動して、有効なユーザーでログインします。
まだ、リダイレクト処理を行っていないので、手動でトップページに遷移します。
OK!レイアウトが切り替わりました。
既存のログインフラグを削除する
front/store/index.js
export const state = () => ({
// 削除
// loggedIn: false,
...
})
export const getters = {}
export const mutations = {
// 削除
// setLoggedIn (state, payload) {
// state.loggedIn = payload
// },
...
}
export const actions = {
// 削除
// login ({ commit }) {
// commit('setLoggedIn', true)
// },
// logout ({ commit }) {
// commit('setLoggedIn', false)
// },
...
}
(コラム)ログイン判定を偽造された場合
上記の実装だと、有効期限とVuexのユーザーオブジェクトを偽造された場合、フロントエンドはログイン状態となります。
しかし、ユーザーが持つDBの値をRailsにリクエストする際に、正しいアクセストークンが無いとエラーになります。
例えば、今のプロジェクト一覧はVuexに直接作成していますが、本来このデータはログイン直後にRailsへリクエストを行い、取得するものです。
その際に、正しいトークンがなければ認証エラーを返し、強制ログアウトが実行されます。
結局、トークンが盗まれない限り、不正なユーザーがログイン状態を維持する事はできません。
フロントエンドのログイン判定はすぐ破られるものだと想定し、それ以後の対応をどうするかが大切です。
実務では、ログイン前とログイン後を完全に切り分けて実装を行うことがセオリーのようです。(筆者はまだやったことがない)
例えば、ブログやECサイトの商品などを管理するcontentfulでは、
- ログイン前のドメインを「www.contentful.com」
- ログイン後のドメインを「app.contentful.com」
として、ログイン前とログイン後を切り分けて実装しています。
ログイン前のユーザーは「app.contentful.com」に紐づくHTMLやCookie、ローカルストレージには一切アクセスできません。
この実装を行うには、
-
フロントエンドのアプリを2つ用意する方法
-
1アプリで複数ドメインを管理する方法
が考えられます。
その他に切り分ける方法として、
-
webpackでエントリーポイントを切り替える方法(Nuxtの実装は未確認)
があるようです。
個人的にはドメインを分けたアプリを2つ用意したほうが、後のブログやドキュメントの追加が容易になる為、良いかなと思っています。
もちろん、絶対に「ログイン前とログイン後を完全に切り分ける必要がある」訳でもないですし、アプリの内容によっては今回の実装でも十分に対応できます。
まずは、作りたいアプリの参考サイトを探して、その実装がどうなっているかCookieやストレージを調べてみると良いですね。ご参考までに。
(コラム終わり)
ログインしたままの処理の設計
現状の問題点
現状、ページをリロードすると、Vuexの値は初期化されます。
このままだと、ログイン中でもリロード毎にユーザーが存在しなくなり、ログアウトしてしまいます。
front/store/index.js
export const state = () => ({
current: {
user: null // Vuexの値はリロードすると初期化される
}
})
解決策
そこで、Nuxt.jsが初期化された(リロードされた)直後に
- 有効期限内であれば、
- Cookieのトークンを使ってRailsにリクエストを行い
- ユーザーが返ってきたら => Vuexに保存
- ユーザーが返ってこなかったら => 有効期限を削除し、ログイン前の状態に戻す
- 有効期限外であれば、
- 何もしない
プラグインファイルを作成します。
ログイン継続処理を行うプラグインを作成する
「plugins」ディレクトリ内に、ログインしたままの処理を実行する
root $ touch front/plugins/nuxtClientInit.js
nuxt.config.jsに登録する
プラグインファイルを
全てのプラグインが準備できた後に読み込む為、4番目に追加します。
front/nuxt.config.js
export default {
...
plugins: [
'plugins/auth',
'plugins/axios',
'plugins/myInject',
// 4番目に追加
'plugins/nuxtClientInit'
],
...
}
Railsにリクエストを送る
有効期限内であれば、Railsにリクエストを送りユーザーを取得します。
front/plugins/nuxtClientInit.js
// client初期設定ファイル
export default async ({ $auth, $axios, store }) => {
if ($auth.isAuthenticated()) {
await $axios.$get('/api/v1/users/current_user')
.then(tokenUser => store.dispatch('getCurrentUser', tokenUser))
// Cookieはサーバーで削除済み
.catch(() => $auth.removeStorage())
}
}
-
.catch(() => $auth.removeStorage())
... トークンが不正な場合など、ユーザーが取得できない時はcatch()
に処理がうつされます。Rails側でCookieは削除されるので、Nuxt.js側ではストレージを削除します。
この時の状態は、
- Vuexのユーザーは
null
で、 - Cookieのトークンは削除され、
- ストレージの有効期限も削除されます。
不正なアクセストークンがリクエストされた場合、完全なログアウト状態を返します。
- Vuexのユーザーは
確認してみよう
front/pages/index.vue
<template>
<div id="logged-in-home">
<!-- 追加 -->
{{ $auth.user }}
...
</template>
..,
確認1)ユーザーが維持できているか?
ログイン状態でページをリロードしてみましょう。
ユーザーが維持できていますね。
これは、リロード時に
確認2)有効期限が無い場合はログアウトしてるか?
ローカルストレージのexp
の値を削除して、リロードしてみましょう。
ログイン前の画面に戻りましたね。
Cookieのトークンは残りますが、次回ログイン時には上書きされます。
確認3)不正なトークンはログアウトしてるか?
もう一度ログインし、Cookieのトークンを改ざんしてリロードしてみましょう。
これも、ログイン前の画面に戻りました。
不正なアクセストークンが送られてきたので
- Cookieのトークン
- ローカルストレージの有効期限
双方とも削除されています。
テストコードを削除する
ここまでの確認が取れたら
front/pages/index.vue
<template>
<div id="logged-in-home">
<!-- {{ $auth.user }} 削除 -->
...
</template>
ユーザー名を追加する
ユーザーが維持できるようになったので、アカウントメニューにユーザー名を追加します。
「components」ディレクトリ内の
front/components/loggedIn/header/loggedInAppBar.vue
<template>
<v-app-bar
app
dense
elevation="1"
clipped-left
color="white"
>
...
<v-list dense>
<v-subheader>
ログイン中のユーザー
</v-subheader>
<v-list-item>
<v-list-item-content>
<v-list-item-subtitle>
<!-- 追加 -->
{{ $auth.user.name }}
<!-- ユーザー名が表示されます 削除 -->
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-divider />
<v-subheader>
アカウント
</v-subheader>
...
</v-app-bar>
</template>
...
ログイン状態でユーザー名が表示されるようになりました。
ログアウトを実装する
ログアウト時には、logout()
メソッドを呼び出します。
これにより、
- Cookieトークン
- ローカルストレージの有効期限
- Vuexのユーザー
が削除されます。
logout.vueにメソッドを追加する
deforeCreate()
に追記します。
コードを簡潔にするために、middleware
の処理は削除しました。
front/pages/logout.vue
<template>
<div />
</template>
<script>
// 追加
export default {
layout: 'logout',
async beforeCreate () {
await this.$auth.logout()
this.$router.replace('/')
}
}
// 削除
// export default {
// async middleware ({ store, redirect, from }) {
// await store.dispatch('logout')
// if (from.name !== 'index') { return redirect('/') }
// },
// layout: 'logout',
// beforeCreate () {
// this.$router.replace('/')
// }
// }
</script>
ログアウトを確認しよう
「layouts」ディレクトリの
<template>
<v-app>
<!-- 追加 -->
{{ $auth.isUserPresent() ? $auth.user : 'ユーザーはおらん' }}
<wel-app-bar
ログイン後、アカウントのログアウトメニューをクリックします。
Vuexのユーザーは削除されました。
その他にも
- Cookieのアクセストークンと
- ローカルストレージの有効期限が
削除できていることを確認してください。
確認が取れたらテストコードは削除しましょう。
front/layouts/welcome.vue
<template>
<v-app>
<!-- {{ $auth.isUserPresent() ? $auth.user : 'ユーザーはおらん' }} 削除 -->
コミットしとく
以上で実装を終わります。コミットしましょう。
root $ cd front
front $ git add -A && git commit -m "add_nuxtClientInit_plugins" && cd ..
root $
まとめ
今回は、Nuxtにログインフラグを作成し、
- ログインを維持する処理と
- ログアウト処理を実装しました。
ただ、今のままだとログインしなくても、ログイン後のページに自由に行き来できますね。
この辺りの処理を次回以降固めていきます。
次回は?
次回はミドルウェアを使ったログイン前後のリダイレクト処理を行います。
またここでお会いしましょう〜!