はじめに
本記事はAPIをRailsのAPIモードで開発し、フロント側をVue.js 3で開発して、認証基盤にdevise_token_authを用いてトークンベースの認証機能付きのSPAを作るチュートリアルのVue.js編の記事(Navigation Guard編)になります。
前回: RailsAPIモード + devise_token_auth + Vue.js 3で認証機能付きのSPAを作る(Vue.js編その2)
次回: Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(ファイル投稿編)
NavigationGuardとは?
前回までで作成したアプリは、ログイン済みでもログイン画面のURLにアクセスすればアクセスできてしまいますし、ログインが必要なページにログインせずにアクセスできてしまいます。(401エラーが返るので投稿は表示されませんが。)
そういったケースで、
- ログイン済みの場合にログインページにアクセスが来たら別ページへリダイレクト
- ログインが必要なページに未ログインの状態でアクセスが来たらログインページにリダイレクト
させる処理が欲しくなってきます。
ここでNavigationGuardの出番です。
NavigationGuardは一言でいうと「ページ遷移時に任意の処理を挟むための仕組み」かと思います。
Vue.jsのVueRouterはNavigationGuardをサポートしていますので、その仕組みを大いに活用していきたいと思います。
src/router/index.tsにmeta情報を追記する
Vueのルーティングにはmeta情報を付与することができます。
このmeta情報を元に、処理の分岐を行うことができます。
認証が必要なURLにはrequiresAuthを付与し、
meta: { requiresAuth: true }
認証が不要なURLにはrequiresNotAuthを付与します。
meta: { requiresNotAuth: true }
では、src/router/index.tsを編集していきます。
{
path: '/login',
name: 'Login',
component: Login,
meta: { requiresNotAuth: true } // 追加
},
{
path: '/posts',
name: 'Post',
component: Post,
meta: { requiresAuth: true } // 追加
},
{
path: '/posts/new',
name: 'NewPost',
component: NewPost,
meta: { requiresAuth: true } // 追加
}
authorizeToken関数の実装
次に、認証が必要なページにリクエストが来た場合に実行する処理を実装していきます。
以下のコマンドを実行してください。
$ touch src/router/authGuard.ts
作成されたファイルを以下のように修正します。
import { validateToken } from '@/api/auth'
import { NavigationGuardNext, RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'
export const authorizeToken = (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
if (to.matched.some((record: RouteRecordNormalized) => record.meta.requiresAuth)) {
validateToken()
.then(() => {
next()
})
.catch(() => {
next({ path: '/login' })
})
} else if (to.matched.some((record: RouteRecordNormalized) => record.meta.requiresNotAuth)) {
validateToken()
.then(() => {
next({ path: '/posts' })
})
.catch(() => {
next()
})
}
}
validateToken関数は後ほど定義しますが、クライアントから送信された認証Tokenの有効性を検証するAPIを叩く関数です。
vue-routerからimportしているのはauthorizeToken関数の引数であるtoとnextに型を当てるためです。
型を当てることで、VSCodeを使っているとマウスオーバーすることで補完が効くようになります。
to.matchedとするとRouteRecordNormalizedの配列が返ります。
RouteRecordNormalized型を確認してみます。
export declare interface RouteRecordNormalized {
// 省略
/**
* {@inheritDoc _RouteRecordBase.meta}
*/
meta: Exclude<_RouteRecordBase['meta'], void>;
// 省略
metaプロパティが定義されていることがわかりました。
後はjavaScriptのsome関数でmatches配列の中のRecordの中でmetaがrequiresAuthのものがあれば、という条件とrequiresNotAuthのものがあれば、という条件で分岐しています。
validateToken関数の実装
次にvalidateToken関数を実装していきます。
src/api/auth.tsを編集します。
export const logout = async () => {
return await Client.delete('/auth/sign_out', { headers: getAuthDataFromStorage() })
.then(() => {
removeAuthDataFromStorage()
})
}
// ここから追加
export const validateToken = async () => {
return await Client.get('/auth/validate_token', { headers: getAuthDataFromStorage() })
.then((response) => {
setAuthDataFromResponse(response.headers)
return response.data
})
}
// ここまで
GETリクエストでAPI側の認証Tokenを検証するURLを叩いて、通信に成功したらLocalStorageの情報を更新する関数をコールして、レスポンスのデータを返却する処理を実装しています。
最後にindex.tsの中でrouterに対してbeforeEachでauthorizeToken関数をコールする処理を追加します。
import NewPost from '@/views/NewPost.vue'
import { authorizeToken } from './authGuard' // 追加
// 省略
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
router.beforeEach(authorizeToken) // 追加
動作確認
これで準備完了です。APIサーバーを立ち上げて、vue側のサーバーも立ち上げて動作確認をしてみます。
また、試しにtoオブジェクトの中身を見るためにconsole.logを仕込んでみましょう。
export const authorizeToken = (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
console.log(to) // 追加
if (to.matched.some((record: RouteRecordNormalized) => record.meta.requiresAuth)) {
validateToken()
localhost:8080/posts にアクセスしてみます。
Networkタブで通信内容の確認をすると、正しくvalidate_tokenのリクエストを送信できていることがわかります。
また、きちんとmeta情報としてルーティングにrequiresAuthが付与されています。
一度ログアウトをして動作を確認してみます。ログアウトボタンは今後実装予定のためLocalStorageの値を空にします。
ApplicationタブのLocal Storage配下のURLの上にマウスオーバーし、右クリックすると「clear」という項目が出ますのでそちらをクリックすると空にできます。
もう一度 localhost:8080/postsにアクセスします。
LocalStorageにTokenが保管されておらず、validate_tokenのリクエストが401だったため、Loginページにリダイレクトしています。
再度ログインして、 localhost:8080/login にアクセスを試みても、/postsにリダイレクトすることも確認するといいですね。
まとめ
これで最低限のNavigationGuardの仕組みは実装できました。
今はログイン済みかどうかのみの判定ですが、今後、「管理者かどうか」等の新たなルールで判別することもあるかもしれません。
その時はまた別のguard関数を定義してやりましょう。
次回は画像投稿および表示の機能を実装したいと思います。