LoginSignup
3
2

More than 1 year has passed since last update.

Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(Navigation Guard編)

Last updated at Posted at 2021-03-30

はじめに

本記事は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を使っているとマウスオーバーすることで補完が効くようになります。

navigation-guard1.png

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 にアクセスしてみます。

navigation-guard2.png

Networkタブで通信内容の確認をすると、正しくvalidate_tokenのリクエストを送信できていることがわかります。

navigation-guars3.png

また、きちんとmeta情報としてルーティングにrequiresAuthが付与されています。

一度ログアウトをして動作を確認してみます。ログアウトボタンは今後実装予定のためLocalStorageの値を空にします。
ApplicationタブのLocal Storage配下のURLの上にマウスオーバーし、右クリックすると「clear」という項目が出ますのでそちらをクリックすると空にできます。

もう一度 localhost:8080/postsにアクセスします。

navigation-guard4.png

LocalStorageにTokenが保管されておらず、validate_tokenのリクエストが401だったため、Loginページにリダイレクトしています。

再度ログインして、 localhost:8080/login にアクセスを試みても、/postsにリダイレクトすることも確認するといいですね。

まとめ

これで最低限のNavigationGuardの仕組みは実装できました。

今はログイン済みかどうかのみの判定ですが、今後、「管理者かどうか」等の新たなルールで判別することもあるかもしれません。

その時はまた別のguard関数を定義してやりましょう。

次回は画像投稿および表示の機能を実装したいと思います。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2