KurocoとNuxt.jsで、ログイン画面を構築する
Kurocoを利用したNuxt.jsプロジェクトで、ログイン画面の作成方法を紹介します。
今回は例として、下記流れにてログインユーザーのみコンテンツ一覧ページが閲覧できる処理を実装します。
- API・エンドポイントの作成
- ログインフォーム実装
- ログイン処理実装(APIセキュリティ毎)
前提条件
Nuxt.jsプロジェクトの作成について
このページはKurocoとNuxt.jsでのプロジェクトが構築済みであり、コンテンツ一覧のページが作成されていることを前提としています。 まだ構築していない場合は、下記のチュートリアルを参照してください。
Kurocoビギナーズガイド
KurocoとNuxt.jsで、コンテンツ一覧ページを作成する
APIセキュリティについて
Kurocoでは、APIのセキュリティ方法がいくつか用意されています。
セキュリティ「無し」を選択されている場合には、ログインの必要無くAPIからデータを取得できますが、 何らかのセキュリティを設定している場合、利用者にはフロントエンドのログインフォームから認証/認可をしていただく必要があります。
今回は、代表的なログイン方式として、以下の2つのパターンを例にしてフロントエンドのログインフォームを構築します。
- Cookie
- 動的アクセストークン
セキュリティの種類については、管理画面マニュアル -> API Securityを参照してください。
セキュリティの種類の詳細な確認方法は、Swagger UIを利用して、APIのセキュリティを確認するをご確認ください。
推奨ブラウザについて
本チュートリアルは、動作確認のためGoogle Chromeの開発者ツールを利用しています。 そのため、ブラウザはGoogle Chromeを推奨いたします。
APIの設定
ログイン用のAPIを設定します。
APIの作成
まずはAPIを新規で作成します。
Kuroco管理画面のAPIより「追加」をクリックします。
API作成画面が表示されるので、下記入力し「追加する」をクリックします。
項目 | 設定内容 |
---|---|
タイトル | login |
版 | 1.0 |
ディスクリプション | login用のAPI |
APIが作成されました。
エンドポイントの作成
次にエンドポイントを作成します。今回は下記エンドポイントを作成します。
- loginエンドポイント
- profileエンドポイント
- logoutエンドポイント
- tokenエンドポイント(APIセキュリティが動的アクセストークンの場合のみ)
「新しいエンドポイントの追加」をクリックし、それぞれ作成します。
loginエンドポイントの作成
loginエンドポイントを下記設定にて作成します。
項目 | 設定内容 |
---|---|
パス | login |
カテゴリー | 認証 |
モデル | login v1 |
オペレーション | login_challenge |
設定完了後、「追加する」をクリックしloginエンドポイント完成です。
profileエンドポイントの作成
profileエンドポイントを下記設定にて作成します。
項目 | 設定内容 |
---|---|
パス | profile |
カテゴリー | 認証 |
モデル | login v1 |
オペレーション | profile |
APIリクエスト制限 | GroupAuth:所属しているグループ ログインを許可するグループを選択してください。 |
基本設定:basic_info |
|
設定完了後、「追加する」をクリックしエンドポイント完成です。
profileエンドポイントは、アクセスしているユーザーの情報を(簡易的に)返却するものです。
GroupAuthでの認証を設定しているため、ログイン済みで無い場合は情報を返さずにエラーとなります。
今回の場合は、email,name1,name2を値を返すように設定しており、簡易的なユーザー情報を取得するほかに、ログイン状態のリストアをする際、操作しているユーザーが本当にログイン済みであるのかを検証するためにリクエストします。
logoutエンドポイントの作成
logoutエンドポイントを下記設定にて作成します。
項目 | 設定内容 |
---|---|
パス | logout |
カテゴリー | 認証 |
モデル | login v1 |
オペレーション | logout |
APIリクエスト制限 | None |
設定完了後、「追加する」をクリックしエンドポイント完成です。
tokenエンドポイントの作成
tokenエンドポイントを下記設定にて作成します。
tokenエンドポイントは、APIセキュリティが動的アクセストークンの場合のみ必要になります。 APIセキュリティがCookieの場合、作成する必要はありません。
項目 | 設定内容 |
---|---|
パス | token |
カテゴリー | 認証 |
モデル | login v1 |
オペレーション | token |
APIリクエスト制限 | None |
設定完了後、「追加する」をクリックしエンドポイント完成です。
CORSの設定
次にCORSの設定をします。[CORSを設定する] をクリックします。
CORS_ALLOW_ORIGINSの [Add Origin] をクリックし、下記を追加します。
http://localhost:3000/
- フロントエンドドメイン
CORS_ALLOW_METHODSの [Add Method] をクリックし、下記を追加します。
- GET
- POST
- OPTIONS
CORS_ALLOW_CREDENTIALSの[Allow Credentials]にチェックが入っていることを確認します。
問題なければ [保存する] をクリックします。
以上で、APIの設定が完了です。
ログインフォーム実装
次に、フロントエンドにログインフォームを作成します。
ダミーのログインフォーム実装
まずはAPIとの連携は省いた状態でログイン画面用コンポーネントの作成し、ダミーでのログイン連携処理を実装していきます。
また、お知らせ一覧画面ではログイン済みかどうかのフラグを参照し、ログイン済みでなければログイン画面に画面遷移するように変更します。
まず、ログイン画面用コンポーネントを作成します。
pages/login/index.vue
ファイルを新規作成し、以下を記載してください。
<template>
<form @submit.prevent="login">
<input v-model="email" name="email" type="email" placeholder="email"/>
<input
v-model="password"
name="password"
type="password"
placeholder="password"
/>
<button type="submit">
ログイン
</button>
</form>
</template>
<script>
export default {
data () {
return {
email: '',
password: ''
};
},
methods: {
login () {
console.log(this.email, this.password)
}
},
};
</script>
この状態でnpm run dev
を実行し、http://localhost:3000/login
にアクセスすると簡単なログインフォームが表示されます。
ここまでで、一度ログの確認をします。
Chromeの開発者ツール:コンソールを開いた状態でフォームに下記を入力し、[ログイン]をクリックします。
- email:
test@example.com
- password:password
すると、入力したemailとpasswordがログとしてコンソールに表示されます。
このログに出力された値をログイン用APIに実際にリクエストすることになります。ひとまずAPI連携部分は仮で実装をし、ログイン後の動きを確認します。
1秒間のリクエストをする見せかけのダミー処理を追加作成し、ログインリクエストに成功した場合、画面上で"ログイン成功"と表示されるように、下記のように修正します。
diff --git a/pages/login/index.vue b/pages/login/index.vue
index 44146fc..492b108 100644
--- a/pages/login/index.vue
+++ b/pages/login/index.vue
@@ -1,28 +1,63 @@
<template>
<form @submit.prevent="login">
+ <p v-if="loginStatus !== null" :style="{ color: resultMessageColor }">
+ {{ resultMessage }}
+ </p>
+
<input v-model="email" name="email" type="email" placeholder="email"/>
<input
v-model="password"
name="password"
type="password"
placeholder="password"
/>
<button type="submit">
ログイン
</button>
</form>
</template>
<script>
export default {
data () {
return {
email: '',
- password: ''
+ password: '',
+
+ loginStatus: null,
+ resultMessage: null
};
},
+ computed: {
+ resultMessageColor () {
+ switch (this.loginStatus) {
+ case 'success':
+ return 'green'
+ case 'failure':
+ return 'red'
+ default:
+ return ''
+ };
+ }
+ },
methods: {
- login () {
- console.log(this.email, this.password)
+ async login () {
+ // ダミーリクエスト(1秒待機の後成功/失敗する)
+ const shouldSuccess = true
+ const request = new Promise((resolve, reject) =>
+ setTimeout(
+ () => (shouldSuccess ? resolve() : reject(Error('login failure'))),
+ 1000
+ )
+ )
+
+ try {
+ await request
+ this.loginStatus = 'success'
+ this.resultMessage = 'ログインに成功しました。'
+ } catch (e) {
+ this.loginStatus = 'failure'
+ this.resultMessage = 'ログインに失敗しました。'
+ };
}
},
};
</script>
1秒の待機の後、[ログインに成功しました]が表示されます。
失敗した際にどうなるかを確認します。
ソースコードから、shouldSuccess = true
を shouldSuccess = false
へ変更し、レスポンスがエラーとなる場合を再現確認します。
確認後は、shouldSuccess = true
へ戻してください。
ログイン状態の保持
次にログイン状態を保持できるように実装します。
a. storeの作成
まずはログイン状態をWebアプリ全体で保持しておき、他の画面でも参照できるようstoreを作成します。
store/index.js
ファイルを新規作成し、下記のコードを記載してください。
export const state = () => ({
profile: null
})
export const getters = {
authenticated (state) {
return state.profile !== null
}
}
export const mutations = {
setProfile (state, { profile }) {
state.profile = profile
}
}
getters
のauthenticated
は、後ほど作成していくprofileデータが空かどうかでtrue/falseが返却されるものです。
profileデータが空で無ければログイン状態と判定する想定をしています。
後にログインした時やログイン状態のリストア時にprofileデータを自動取得し、それ以外のログアウトなどで値が設定されないようにしていきます。
b. middlewareの作成
次にmiddlewareを作成します。
middleware/auth.js
を新規作成し、下記のコードを記載してください。
export default async ({ app, store, redirect }) => {
if (!store.getters.authenticated) {
return redirect('/login')
}
await null
}
middlewareは各画面のソースpage/*.vue
が処理をする以前に動作します。
storeのauthenticated
がfalseである場合にはログインページへ強制的にリダイレクトさせます。
c. middlewareの動作確認
middlewareの動作を確認します。
pages/login/index.vue
にニュース一覧ページへのリンクを追加します。
diff --git a/pages/login/index.vue b/pages/login/index.vue
index eb123b4..37d845a 100644
--- a/pages/login/index.vue
+++ b/pages/login/index.vue
@@ -14,6 +14,12 @@
<button type="submit">
ログイン
</button>
+
+ <div>
+ <nuxt-link to="/news">
+ ニュース一覧ページへ
+ </nuxt-link>
+ </div>
</form>
</template>
ニュース一覧画面のpages/news/index.vue
のソースコードを変更して、middlewareを適用します。
diff --git a/pages/news/index.vue b/pages/news/index.vue
index ac8e0fd..dcdd806 100644
--- a/pages/news/index.vue
+++ b/pages/news/index.vue
@@ -10,6 +10,7 @@
<script>
export default {
+ middleware: 'auth',
async asyncData ({ $axios }) {
return {
response: await $axios.$get('/rcms-api/4/news'),
/rcms-api/4/news
の部分はご自身のエンドポイントのURLに変更してください。
以下同様に、ソースコード内のエンドポイントURLはご自身のエンドポイントURLに変更をお願いします。
この処理により、ニュース一覧画面にアクセスするためにはログインが必要になります。 ログインしていない場合は、ニュース一覧ページへアクセスすると強制的にログイン画面へとリダイレクトされるようになります。
次に、ログイン成功時、store
のprfofile
をnull以外の状態へ変更するようにします。
pages/login/index.vue
を下記のように変更します。
diff --git a/pages/login/index.vue b/pages/login/index.vue
index 37d845a..b3cd6a1 100644
--- a/pages/login/index.vue
+++ b/pages/login/index.vue
@@ -59,6 +59,8 @@ export default {
try {
await request
+ this.$store.commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.state.profileに適用
+
this.loginStatus = 'success'
this.resultMessage = 'ログインに成功しました。'
} catch (e) {
ログインページにアクセスし、ログイン操作をしてニュース一覧ページに画面遷移することを確認します。
確認にはVue.js devtoolsを使用しています。
ログイン状態のリストアの実装
これまでの実装によって通常のログイン処理は実装されました。 しかしながら、直接URLアクセスやブラウザで画面更新されたとき、これまでの実装では一度ログインしたはずであるのにも関わらずログイン画面にリダイレクトされる不具合が発生します。
上記の操作では、store
のprofile
はNuxtが初期化されるためnullとなり、
直前に一度ログインしていた場合であってもログイン状態と判定されないためです。
この対応には、一度ログインしたことがある場合にはブラウザのLocalStorageにフラグを設定しておき、
フラグがtrueである場合にstore
のprofile
にダミーのデータを適用するようにします。
/store/index.js
を下記のように修正してください。
diff --git a/store/index.js b/store/index.js
index 1c36f1d..5cca182 100644
--- a/store/index.js
+++ b/store/index.js
@@ -13,3 +13,15 @@ export const mutations = {
state.profile = profile
}
}
+
+export const actions = {
+ async restoreLoginState ({ commit }) {
+ const authenticated = JSON.parse(localStorage.getItem('authenticated'))
+
+ if (!authenticated) {
+ throw new Error('need to login')
+ }
+ commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.
+ await null
+ }
+}
また、/middleware/auth.js
を下記のように修正してください。
diff --git a/middleware/auth.js b/middleware/auth.js
index d3c7ffe..4aa086c 100644
--- a/middleware/auth.js
+++ b/middleware/auth.js
@@ -1,7 +1,9 @@
export default async ({ app, store, redirect }) => {
if (!store.getters.authenticated) {
- return redirect('/login')
+ try {
+ await store.dispatch('restoreLoginState')
+ } catch (err) {
+ return redirect('/login')
+ }
}
-
- await null
}
ニュース一覧ページにアクセスし、下記4点を確認します。
- LocalStorageの
authenticated
がtrue以外である場合、ログインページにリダイレクトされること - LocalStorageの
authenticated
がtrueである場合、ログインページにリダイレクトされないこと - LocalStorageの
authenticated
がtrueかつブラウザの画面更新をした場合でも、ログインページにリダイレクトされないこと - LocalStorageの
authenticated
をfalseにしてブラウザの画面更新をすると、ログインページにリダイレクトされること
今回はLocalStorageの状態を、chromeの開発者ツールの[Application]タブにて確認します。
chromeの開発者ツールより[Application]タブをクリックし、[Storage] -> [Local Storage] -> [http://localhost:3000]をクリックします。
ログインページよりログイン後、Keyにauthenticated
、Valueにtrue
またはfalse
を入力し、上記4点の動作を確認します。
ログイン動作修正
次にログイン動作を修正します。
ログイン成功時にLocalStorageのauthenticated
をtrueにさせます。また、今後の修正に備えてログイン処理を一部store
に移動します。
/pages/login/index.vue
を下記のように修正します。
diff --git a/pages/login/index.vue b/pages/login/index.vue
index b3cd6a1..25f6a8c 100644
--- a/pages/login/index.vue
+++ b/pages/login/index.vue
@@ -48,18 +48,12 @@ export default {
},
methods: {
async login () {
- // ダミーリクエスト(1秒待機の後成功/失敗する)
- const shouldSuccess = true
- const request = new Promise((resolve, reject) =>
- setTimeout(
- () => (shouldSuccess ? resolve() : reject(Error('login failure'))),
- 1000
- )
- )
-
try {
- await request
- this.$store.commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.state.profileに適用
-
+ const payload = {
+ email: this.email,
+ password: this.password
+ }
+ await this.$store.dispatch('login', payload)
this.loginStatus = 'success'
this.resultMessage = 'ログインに成功しました。'
次に/store/index.js
を下記のように修正します。
diff --git a/store/index.js b/store/index.js
index 5cca182..b09c428 100644
--- a/store/index.js
+++ b/store/index.js
@@ -11,10 +11,29 @@ export const getters = {
export const mutations = {
setProfile (state, { profile }) {
state.profile = profile
+ },
+ updateLocalStorage (state, payload) {
+ Object.entries(payload).forEach(([key, val]) => {
+ localStorage.setItem(key, val)
+ })
}
}
export const actions = {
+ async login ({ commit }, payload) {
+ // ダミーリクエスト(1秒待機の後成功/失敗する)
+ const shouldSuccess = true
+ const request = new Promise((resolve, reject) =>
+ setTimeout(
+ () => (shouldSuccess ? resolve() : reject(Error('login failure'))),
+ 1000
+ )
+ )
+ await request
+
+ commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.state.profileに適用
+ commit('updateLocalStorage', { authenticated: true })
+ },
async restoreLoginState ({ commit }) {
const authenticated = JSON.parse(localStorage.getItem('authenticated'))
ログイン成功時にauthenticated
がtrueになることを確認します。
以上でフロントエンドの実装を終了します。
次にAPIを実装します。 なお、実装はAPIセキュリティ毎に実装方法が変わります。 今回はAPIセキュリティがCookieの場合と、動的アクセストークンの場合の実装方法を記載します。 ご自身のAPIセキュリティに併せて、それぞれの対応方法をご確認ください。
A. ログイン処理実装(APIセキュリティがCookieの場合)
次に、先ほどダミーで作成していたログイン処理をloginエンドポイントへとアクセスするように変更します。 まずはAPIセキュリティがCookieの場合の実装方法を説明します。 Kuroco管理画面より、[API] -> [login] をクリックし、「セキュリティ」をクリックしてください。
「セキュリティ」よりCookieを選択し、「保存する」をクリックしてください。
loginエンドポイントへのリクエスト実装
store/index.js
を下記に修正します。
diff --git a/store/index.js b/store/index.js
index b09c428..45982c8 100644
--- a/store/index.js
+++ b/store/index.js
@@ -21,15 +21,7 @@ export const mutations = {
export const actions = {
async login ({ commit }, payload) {
- // ダミーリクエスト(1秒待機の後成功/失敗する)
- const shouldSuccess = true
- const request = new Promise((resolve, reject) =>
- setTimeout(
- () => (shouldSuccess ? resolve() : reject(Error('login failure'))),
- 1000
- )
- )
- await request
+ await this.$axios.$post('/rcms-api/9/login', payload)
commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.state.profileに適用
commit('updateLocalStorage', { authenticated: true })
次に、loginエンドポイントへリクエストされているか確認します。
ログインページを開き、Chromeの開発者ツール:ネットワークを開いた状態でログイン処理を行います。 すると、loginエンドポイントへとリクエストされていることが確認できます。
cookieの有効化
クロスオリジンでのcookieを有効化するため、nuxt.config.js
を下記のように修正してください。
diff --git a/nuxt.config.js b/nuxt.config.js
index 56cd22f..0445d45 100644
--- a/nuxt.config.js
+++ b/nuxt.config.js
@@ -51,9 +51,9 @@ export default {
// Axios module configuration: https://go.nuxtjs.dev/config-axios
- axios: {},
+ axios: {
+ baseURL: process.env.BASE_URL,
+ credentials: true,
+ withCredentials: true
+ },
// Build Configuration: https://go.nuxtjs.dev/config-build
profileエンドポイントへのリクエスト/ハンドリング実装
今までの実装では、ブラウザのLocalStorageのauthenticated
フラグによってログイン済かどうかを判断する実装をしています。
しかしながら、LocalStorageはブラウザ上で簡単に改ざんが可能です。
またセッション有効期限によってauthenticated
がtrueであっても、実際には他のエンドポイントへのリクエストがアクセスエラーとなる場合もあります。
これらによる誤動作を防ぐため、profileのAPIにリクエストし、ユーザー情報が返ってくるか否かを確認することで二重のチェックを行います。
二重のチェックは、profileエンドポイントである必要はありませんが、ログイン中のユーザー名を表示する等、profileが返すデータを最初に必要とするユースケースが多いため、profileエンドポイントの利用が、スタンダードになっています。
/store/index.js
を下記のように修正します。
--- a/store/index.js
+++ b/store/index.js
@@ -24,7 +24,13 @@ export const mutations = {
export const actions = {
async login({ commit }, payload) {
await this.$axios.$post('/rcms-api/9/login', payload)
- commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.state.profileに適用
+ const profileRes = await this.$axios.$get('/rcms-api/9/profile')
+ commit('setProfile', { profile: profileRes })
commit('updateLocalStorage', { authenticated: true })
},
async restoreLoginState({ commit }) {
const authenticated = JSON.parse(localStorage.getItem('authenticated'))
if (!authenticated) {
throw new Error('need to login')
}
- commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.
- await null
+
+ const profileRes = await this.$axios.$get('/rcms-api/9/profile')
+ commit('setProfile', { profile: profileRes })
}
}
修正ができたらリストアの動作を確認します。
ログインページを開き、Chromeの開発者ツール:アプリケーションを開いた状態でログイン処理を行います。
すると、authenticated
がtrue
となります。
この状態で、「ニュース一覧ページへ」をクリックし画面遷移します。
今までの実装と同じように、authenticated
がtrue
のまま、ニュース一覧ページの表示を確認できます。
logoutエンドポイントへのリクエスト/ハンドリング実装
次に、ログアウト処理を実装します。
Kuroco側でセッションが残っていながらフロント側で再ログインした場合など、予期せぬ動作が発生する可能性もあります。
そのため、ログイン状態ではないと判定する場合はAPIへログアウト状態にするようリクエストする必要があります。
/store/index.js
を下記のように修正します。
diff --git a/store/index.js b/store/index.js
index 296c4dc..068e184 100644
--- a/store/index.js
+++ b/store/index.js
@@ -27,13 +27,29 @@ export const actions = {
commit('setProfile', { profile: profileRes })
commit('updateLocalStorage', { authenticated: true })
},
- async restoreLoginState ({ commit }) {
+ async logout ({ commit }) {
+ try {
+ await this.$axios.$post('/rcms-api/9/logout')
+ } catch {
+ /** No Process */
+ /** エラーが返却されてきた場合は、結果的にログアウトできているものとみなし、これを無視します。 */
+ }
+ commit('setProfile', { profile: null })
+ commit('updateLocalStorage', { authenticated: false })
+
+ this.$router.push('/login')
+ },
+ async restoreLoginState ({ commit, dispatch }) {
const authenticated = JSON.parse(localStorage.getItem('authenticated'))
if (!authenticated) {
+ await dispatch('logout')
+ throw new Error('need to login')
+ }
+ try {
+ const profileRes = await this.$axios.$get('/rcms-api/9/profile')
+ commit('setProfile', { profile: profileRes })
+ } catch {
+ await dispatch('logout')
throw new Error('need to login')
}
- const profileRes = await this.$axios.$get('/rcms-api/9/profile')
- commit('setProfile', { profile: profileRes })
}
}
また、ニュース一覧画面を以下のように修正し、ログアウトボタンを作成します。
diff --git pages/news/index.vue pages/news/index.vue
index dcdd806..e79e075 100644
--- pages/news/index.vue
+++ pages/news/index.vue
@@ -1,23 +1,31 @@
<template>
<div>
<p>ニュース一覧ページ</p>
+ <button type="button" @click="logout">
+ ログアウト
+ </button>
<div v-for="n in response.list" :key="n.slug">
<nuxt-link :to="`/news/${n.topics_id}`">
{{ n.ymd }} {{ n.subject }}
</nuxt-link>
</div>
</div>
</template>
<script>
+import { mapActions } from 'vuex';
+
export default {
middleware: 'auth',
async asyncData ({ $axios }) {
return {
response: await $axios.$get('/rcms-api/4/news'),
};
},
+ methods: {
+ ...mapActions(['logout'])
+ },
};
</script>
ログイン状態のニュース一覧画面にてログアウトボタンをクリックすると、下記となることを確認します。
- logoutエンドポイントへリクエストしている
- ログイン画面に遷移する
- そのままログインせずにニュース一覧画面へアクセスすると、ログイン画面に自動遷移される
以上でAPIセキュリティがcookieの場合のログイン処理の実装が完了です。
B. ログイン処理実装(APIセキュリティが動的アクセストークンの場合)
次に、先ほどダミーで作成していたログイン処理をloginエンドポイントへとアクセスするように変更します。 ここではAPIセキュリティが動的アクセストークンの場合の実装方法を説明します。 Kuroco管理画面より、[API] -> [login] をクリックし、「セキュリティ」をクリックしてください。
「セキュリティ」より動的アクセストークンを選択し、「保存する」をクリックしてください。
login,tokenエンドポイントへのリクエスト実装
store/index.js
を下記に修正します。
diff --git a/store/index.js b/store/index.js
index b09c428..64e6e2d 100644
--- a/store/index.js
+++ b/store/index.js
@@ -21,15 +21,11 @@ export const mutations = {
export const actions = {
async login ({ commit }, payload) {
- // ダミーリクエスト(1秒待機の後成功/失敗する)
- const shouldSuccess = true
- const request = new Promise((resolve, reject) =>
- setTimeout(
- () => (shouldSuccess ? resolve() : reject(Error('login failure'))),
- 1000
- )
+ const { grant_token } = await this.$axios.$post('/rcms-api/9/login', payload)
+ const { access_token } = await this.$axios.$post(
+ '/rcms-api/9/token',
+ { grant_token }
)
- await request
commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.state.profileに適用
commit('updateLocalStorage', { authenticated: true })
loginエンドポイントとtokenエンドポイントへリクエストされているか確認します。
ログインページを開き、Chromeの開発者ツール:ネットワークを開いた状態でログイン処理を行います。 すると、loginエンドポイントとtokenエンドポイントへとリクエストされていることが確認できます。
tokenの保持
ここまでは、ログインしているかどうかをLocalStorageのauthenticated
のフラグ値で判定していました。
しかし、動的アクセストークンでは認証を要求するエンドポイントには実際のtoken値が必要になります。
そのため、authenticated
をtoken
へ変更し、token値を保持するようにします。
store/index.js
を下記に修正します。
diff --git store/index.js store/index.js
index 64e6e2d..9b048c5 100644
--- store/index.js
+++ store/index.js
@@ -1,42 +1,50 @@
export const state = () => ({
profile: null
})
export const getters = {
authenticated (state) {
return state.profile !== null
}
}
export const mutations = {
setProfile (state, { profile }) {
state.profile = profile
},
updateLocalStorage (state, payload) {
Object.entries(payload).forEach(([key, val]) => {
localStorage.setItem(key, val)
})
+ },
+ setAccessTokenOnRequestHeader (state, { rcmsApiAccessToken }) {
+ this.$axios.defaults.headers.common = {
+ 'X-RCMS-API-ACCESS-TOKEN': rcmsApiAccessToken
+ }
}
}
export const actions = {
async login ({ commit }, payload) {
const { grant_token } = await this.$axios.$post('/rcms-api/9/login', payload)
const { access_token } = await this.$axios.$post(
'/rcms-api/9/token',
{ grant_token }
)
+ commit('updateLocalStorage', { rcmsApiAccessToken: access_token.value })
+ commit('setAccessTokenOnRequestHeader', { rcmsApiAccessToken: access_token.value })
+
commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.state.profileに適用
- commit('updateLocalStorage', { authenticated: true })
},
async restoreLoginState ({ commit }) {
- const authenticated = JSON.parse(localStorage.getItem('authenticated'))
+ const rcmsApiAccessToken = localStorage.getItem('rcmsApiAccessToken')
+ const authenticated = typeof rcmsApiAccessToken === 'string' && rcmsApiAccessToken.length > 0
if (!authenticated) {
throw new Error('need to login')
}
commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.
await null
}
}
ログイン成功後の動きを確認します。
ログインページを開き、Chromeの開発者ツール:アプリケーションを開いた状態でログイン処理を行います。 すると、rcmsApiAccessToken
に値が保存されます。
profileエンドポイントへのリクエスト/ハンドリング実装
今までの実装では、ブラウザのLocalStorageのrcmsApiAccessToken
フラグによってログイン済かどうかを判断する実装をしています。
しかしながら、LocalStorageはブラウザ上で簡単に改ざんが可能です。
またセッション有効期限によってrcmsApiAccessToken
がtrueであっても、実際には他のエンドポイントへのリクエストがアクセスエラーとなる場合もあります。
これらによる誤動作を防ぐため、APIへアクセスすることによって、もう1クッションの追加確認をします。
そのため、/store/index.js
を下記のように修正します。
diff --git store/index.js store/index.js
index 9b048c5..c64b3a9 100644
--- store/index.js
+++ store/index.js
@@ -1,50 +1,57 @@
export const state = () => ({
profile: null
})
export const getters = {
authenticated (state) {
return state.profile !== null
}
}
export const mutations = {
setProfile (state, { profile }) {
state.profile = profile
},
updateLocalStorage (state, payload) {
Object.entries(payload).forEach(([key, val]) => {
localStorage.setItem(key, val)
})
},
setAccessTokenOnRequestHeader (state, { rcmsApiAccessToken }) {
this.$axios.defaults.headers.common = {
'X-RCMS-API-ACCESS-TOKEN': rcmsApiAccessToken
}
}
}
export const actions = {
async login ({ commit }, payload) {
const { grant_token } = await this.$axios.$post('/rcms-api/9/login', payload)
const { access_token } = await this.$axios.$post(
'/rcms-api/9/token',
{ grant_token }
)
commit('updateLocalStorage', { rcmsApiAccessToken: access_token.value })
commit('setAccessTokenOnRequestHeader', { rcmsApiAccessToken: access_token.value })
- commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.state.profileに適用
+ const profileRes = await this.$axios.$get('/rcms-api/9/profile')
+ commit('setProfile', { profile: profileRes })
},
async restoreLoginState ({ commit }) {
const rcmsApiAccessToken = localStorage.getItem('rcmsApiAccessToken')
const authenticated = typeof rcmsApiAccessToken === 'string' && rcmsApiAccessToken.length > 0
if (!authenticated) {
throw new Error('need to login')
}
- commit('setProfile', { profile: {} }) // ダミーのオブジェクトをstore.
- await null
+
+ try {
+ commit('setAccessTokenOnRequestHeader', { rcmsApiAccessToken })
+ const profileRes = await this.$axios.$get('/rcms-api/9/profile')
+ commit('setProfile', { profile: profileRes })
+ } catch {
+ throw new Error('need to login')
+ }
}
}
ログイン後、ブラウザの画面更新をしてニュース一覧画面に遷移し、ログイン状態がリストアされることを確認します。
ログインページを開き、Chromeの開発者ツール:アプリケーションを開いた状態でログイン処理を行います。 すると、rcmsApiAccessToken
に値が保存されます。
また、この状態で、「ニュース一覧ページへ」をクリックし画面遷移しても、rcmsApiAccessToken
に値が保存されたままであることを確認できます。
さらに、LocalStorageのrcmsApiAccessToken
をChromeの開発者ツールより修正した場合、リストア時にログイン画面へ強制的に画面遷移されることが確認できます。
logoutエンドポイントへのリクエスト/ハンドリング実装
次に、ログアウト処理を実装します。
Kuroco側でセッションが残っていながらフロント側で再ログインした場合など、予期せぬ動作が発生する可能性もあります。
そのため、ログイン状態ではないと判定する場合はAPIへログアウト状態にするようリクエストする必要があります。
/store/index.js
を下記のように修正します。
diff --git a/store/index.js b/store/index.js
index c64b3a9..0e97247 100644
--- a/store/index.js
+++ b/store/index.js
@@ -38,19 +38,32 @@ export const actions = {
const profileRes = await this.$axios.$get('/rcms-api/9/profile')
commit('setProfile', { profile: profileRes })
},
- async restoreLoginState ({ commit }) {
+ async logout ({ commit }) {
+ try {
+ await this.$axios.$post('/rcms-api/9/logout')
+ } catch {
+ /** No Process */
+ /** エラーが返却されてきた場合は、結果的にログアウトできているものとみなし、これを無視します。 */
+ }
+ commit('setProfile', { profile: null })
+ commit('updateLocalStorage', { rcmsApiAccessToken: null })
+ commit('setAccessTokenOnRequestHeader', { rcmsApiAccessToken: null })
+
+ this.$router.push('/login')
+ },
+ async restoreLoginState ({ commit, dispatch }) {
const rcmsApiAccessToken = localStorage.getItem('rcmsApiAccessToken')
const authenticated = typeof rcmsApiAccessToken === 'string' && rcmsApiAccessToken.length > 0
if (!authenticated) {
+ await dispatch('logout')
throw new Error('need to login')
}
try {
commit('setAccessTokenOnRequestHeader', { rcmsApiAccessToken })
const profileRes = await this.$axios.$get('/rcms-api/9/profile')
commit('setProfile', { profile: profileRes })
} catch {
+ await dispatch('logout')
throw new Error('need to login')
}
}
また、ニュース一覧画面を以下のように修正し、ログアウトボタンを作成します。
diff --git pages/news/index.vue pages/news/index.vue
index dcdd806..e79e075 100644
--- pages/news/index.vue
+++ pages/news/index.vue
@@ -1,23 +1,31 @@
<template>
<div>
<p>ニュース一覧ページ</p>
+ <button type="button" @click="logout">
+ ログアウト
+ </button>
<div v-for="n in response.list" :key="n.slug">
<nuxt-link :to="`/news/${n.topics_id}`">
{{ n.ymd }} {{ n.subject }}
</nuxt-link>
</div>
</div>
</template>
<script>
+import { mapActions } from 'vuex';
+
export default {
middleware: 'auth',
async asyncData ({ $axios }) {
return {
response: await $axios.$get('/rcms-api/4/news'),
};
},
+ methods: {
+ ...mapActions(['logout'])
+ },
};
</script>
ログイン状態のニュース一覧画面にてログアウトボタンをクリックすると、下記となることを確認します。
- logoutエンドポイントへリクエストしている
- ログイン画面に遷移する
- そのままログインせずにニュース一覧画面へアクセスすると、ログイン画面に自動遷移される
以上でAPIセキュリティが動的アクセストークンの場合のログイン処理の実装が完了です。
参考
以上でKurocoを利用したNuxt.jsプロジェクトで、ログイン画面の作成方法の紹介を終わります。
今回は基本的な説明のため、簡単にログイン画面を作成して最低限のログイン制御を実現しました。
実際に利用する際には、フォームのバリデーション処理や、@nuxt/auth
などのライブラリをご利用いただく必要性が考えられますが、基本的なログイン構築の流れの理解としてご利用いただければ幸いです。
サポート
お探しのページは見つかりましたか?解決しない場合は、問い合わせフォームからお問い合わせいただくか、Slackコミュニティにご参加ください。