こんにちは。KOUKIです。
前回に引き続き、ユーザー認証アプリの実装を行います。本記事は、主にフロントエンドで以下の処理について書きます。
1. Vuexで状態管理
2. ログアウト機能
3. Forgot機能
4. Password Reset機能
作業を始める前に、コンテナを起動しておきましょう。
1 2 |
// コンテナの起動 docker-compose up |
コンテナを立ち上げたら、「localhost:8080」にアクセスしましょう。
<目次>
前回
Vuexで状態管理
Vuexでログイン状態を一元管理します。
ui/src/store配下のindex.jsに以下の実装をしてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { Commit, createStore } from 'vuex' export default createStore({ state: { auth: false }, mutations: { setAuth: (state: { auth: boolean }, auth: boolean) => state.auth = auth }, actions: { // ログイン済みかどうかの状態を管理する setAuth: ({commit}: { commit: Commit }, auth: boolean) => commit('setAuth', auth) }, modules: { } }) |
この実装で、ログイン状態を管理します。authには、ログイン済->true、ログアウト->falseが入る設計です。
mutationsは、プログラム内で状態が変化したことを検知するためのもので、actionsと紐づいたパラメータ名(setAuth)でなければなりません。actionsでは、authパラメータの状態を変化させます。
理解を深めるために、実際に使ってみましょう。
Home.vueを次のように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// Home.vue ... <script> import { ref, onMounted } from 'vue' import axios from 'axios' import {useStore} from 'vuex' export default { name: "Home", setup() { const message = ref('You are not logged in!') const store = useStore() onMounted(async () => { try { const { data } = await axios.get('user') message.value = `Hi ${data.first_name} ${data.last_name}` // actionsに設定したパラメータ名を設定 await store.dispatch('setAuth', true) } catch(e) { await store.dispatch('setAuth', false) } }) return { message } } } </script> |
Vuexを使うには、useStoreをインポートします。
1 |
import {useStore} from 'vuex' |
あとはそれをインスタンス化し、dispatchメソッドを実行して変化させたい状態をセットするだけです。
1 2 3 |
const store = useStore() // actionsに設定したパラメータ名を設定 await store.dispatch('setAuth', true) |
ログアウト機能
ログイン状態を管理できる様になったので、ログアウトを実装しましょう。Nav.vueを修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
// Nav.vue <template> <nav class="navbar navbar-expand-md navbar-dark bg-dark"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <router-link to="/" class="nav-link">Home</router-link> </li> </ul> <ul class="navbar-nav my-2 my-lg-0"> <!-- ログイン済みなら表示 --> <template v-if="auth"> <li class="nav-item"> <router-link to="/login" class="nav-link" @click="logout">Logout</router-link> </li> </template> <!-- 未ログインなら非表示 --> <template v-if="!auth"> <li class="nav-item"> <router-link to="/login" class="nav-link">Login</router-link> </li> <li class="nav-item"> <router-link to="/register" class="nav-link">Register</router-link> </li> </template> </ul> </nav> </template> <script> import { computed } from 'vue' import { useStore } from 'vuex' import { useRouter } from 'vue-router' import axios from 'axios' export default { name: "Nav", setup() { const store = useStore() const router = useRouter() const auth = computed(() => store.state.auth) const logout = async () => { await axios.post('logout', {}) store.dispatch('setaAuth', false) await router.push('/login') } return { auth } } } </script> |
HTMLの以下の部分では、ログイン状態によって、表示するヘッダを切り替えてます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<ul class="navbar-nav my-2 my-lg-0"> <!-- ログイン済みなら表示 --> <template v-if="auth"> <li class="nav-item"> <router-link to="/login" class="nav-link" @click="logout">Logout</router-link> </li> </template> <!-- 未ログインなら非表示 --> <template v-if="!auth"> <li class="nav-item"> <router-link to="/login" class="nav-link">Login</router-link> </li> <li class="nav-item"> <router-link to="/register" class="nav-link">Register</router-link> </li> </template> |
JavaScript側の処理では、logout apiへリクエストを送信しています。
そして、store.dispatchメソッドで未ログイン状態に戻し、ログイン画面に戻る処理を実装しました。
Forgot機能
パスワードを忘れた時の機能を実装しましょう。
ページの作成
Forgot.vueにページを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
// Forgot.vue <template> <main class="form-forgot"> <form @submit.prevent="forgot"> <div v-if="notify.cls" :class="`alert alert-${notify.cls}`" role="alert"> {{ notify.message }} </div> <h1 class="h3 mb-3 fw-normal">Please set your email</h1> <input v-model="email" type="email" class="form-control mb-3" placeholder="Email" required> <button class="w-100 btn btn-lg btn-primary" type="submit">Login</button> </form> </main> </template> <script> export default { } </script> <style> .form-forgot { width: 100%; max-width: 330px; padding: 15px; margin: auto; } .form-forgot .form-control { position: relative; box-sizing: border-box; height: auto; padding: 10px; font-size: 16px; } .form-forgot .form-control:focus { z-index: 2; } .form-forgot input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-forgot input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; } </style> |
ほとんどログインページと同じです。次に設定するrouteを実装すれば、以下の様に表示されます。

routeの作成
src/router/index.jsにForgotページへのルートを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// router/index.js import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' import Home from '../pages/Home.vue' import Login from '../pages/Login.vue' import Register from '../pages/Register.vue' import Forgot from '../pages/Forgot.vue' // 追加 const routes: Array<RouteRecordRaw> = [ { path: '/', component: Home}, { path: '/login', component: Login}, { path: '/register', component: Register}, { path: '/forgot', component: Forgot}, // 追加 ] |
ログイン画面からこのルートを介して、Forgotページを表示できるようにしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// Login.vue <template> <main class="form-login"> <form @submit.prevent="login"> <h1 class="h3 mb-3 fw-normal">Login</h1> <input v-model="email" type="email" class="form-control" placeholder="Email" required> <input v-model="password" type="password" class="form-control" placeholder="Password" required> <!-- 追加 --> <div class="mb-2"> <router-link to="/forgot">Forgot Password?</router-link> </div> <button class="w-100 btn btn-lg btn-primary" type="submit">Login</button> </form> </main> </template> |
Email送信
forgot apiへリクエストを送って、メールを送信してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<script> import { reactive, ref } from 'vue' import axios from 'axios' export default { name: "Forgot", setup() { const email = ref('') // メール送信結果を格納 const notify = reactive({ cls: '', message: '' }) const forgot = () => { try { // forgot apiへリクエスト送信 axios.post('forgot', { email: email.value }) notify.cls = 'success' notify.message = 'Email was sent!' }catch (e) { notify.cls = 'danger' notify.message = 'Email does not exit!' } } return { email, notify, forgot } } } </script> |
個人的に「reactive」関数がめっちゃ使えると思いました。この関数で定義した変数に変化があった時、画面にリアルタイムに反映してくれるみたいです。
Email送信機能は、MailHogを使って実装しています。
動作確認
今回作成した機能の動作確認をしましょう。
Reset Password機能
続いて、パスワードをリセットする機能を実装します。
ページの作成
最初に、Reset Passwordページを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// Reset.vue <template> <main class="form-reset"> <form @submit.prevent="submit"> <h1 class="h3 mb-3 fw-normal">Please Reset Your Password</h1> <input v-model="password" type="password" class="form-control" placeholder="Password" required> <input v-model="passwordConfirm" type="password" class="form-control" placeholder="Password Confirm" required> <button class="w-100 btn btn-lg btn-primary" type="submit">Submit</button> </form> </main> </template> <style> .form-reset { width: 100%; max-width: 330px; padding: 15px; margin: auto; } .form-reset .form-control { position: relative; box-sizing: border-box; height: auto; padding: 10px; font-size: 16px; } .form-reset .form-control:focus { z-index: 2; } .form-reset input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-reset input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; } </style> |
これは、Registerページとほぼ同じです。
routeの作成
router/index.jsにResetページへのルートを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// router/index.js import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' import Home from '../pages/Home.vue' import Login from '../pages/Login.vue' import Register from '../pages/Register.vue' import Forgot from '../pages/Forgot.vue' import Reset from '../pages/Reset.vue' // 追加 const routes: Array<RouteRecordRaw> = [ { path: '/', component: Home}, { path: '/login', component: Login}, { path: '/register', component: Register}, { path: '/forgot', component: Forgot}, { path: '/reset/:token', component: Reset}, // 追加 ] |
他のパスと比べると「’/reset/:token’」は少し違っています。これは、動的ルートマッピングを使っています。こうすると「http://localhost:8080/reset/NVlgTeMaPEZQ」のような感じで、任意の文字列付きでリクエストを送信できます。
パスワードをリセット
最後にパスワードリセット処理を実装して完了です。とは言っても、Forgotの時とほぼ同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<script> import { ref } from 'vue' import { useRoute, useRouter } from 'vue-router' import axios from 'axios' export default { name: "Reset", setup() { const password = ref('') const passwordConfirm = ref('') const route = useRoute() const router = useRouter() const submit = async () => { await axios.post('reset', { // urlからtokenを取得 token: route.params.token, password: password.value, password_confirm: passwordConfirm.value }) // ログイン画面へ遷移する await router.push('/login') } return { password, passwordConfirm, submit } } } </script> |
面白いのは、URLからTokenを取得する処理ですね。
useRouteでインスタンス化します。
1 |
const route = useRoute() |
そして、以下のパラメーターからTokenを取得できます。
1 |
token: route.params.token |
動作確認
MailHogからPassword Resetページへ遷移 -> パスワード変更 -> ログインの動きを確認してみましょう。
これで、完成です。お疲れ様でした!
おわりに
長くなりましたが、ようやく完成です。
UIが正直ダサいですけど、ユーザー認証機能は網羅できたのかなと思います^^
それでは、また!
コメントを残す
コメントを投稿するにはログインしてください。