Udemy 13. フロントエンドのログイン認証 #05
2020年11月03日に公開

【Nuxt.js】ログイン前後のリダイレクト処理をミドルウェアで実装する

今回達成すること

今回は、ログイン前後のリダイレクト処理を行います。

また、ログイン前のユーザーがアクセスしようとしたURLを記憶し、ログイン直後に記憶ルートに遷移する実装も行います。

リダイレクト処理の設計

  • ログインのユーザーの場合、
    • ログイン後ページにアクセスしようとすると、ログインページにリダイレクトします。
    • その時のルートは、Vuexに保存します。
  • ログインのユーザーの場合、
    • ログインページ、会員登録ページにアクセスすると、ホームにリダイレクトします。

nuxt auth4

ログイン直後の実装

ログインが成功すれば、

  • ユーザーがアクセスしようとした記憶ルートにリダイレクトします。
  • 上記ルートがなければトップページにリダイレクトします。

これらは、Nuxt.jsのミドルウェアを使って実装を行います。

(コラム)middlewareとは

middleware(ミドルウェア)には、ページをレンダリングする前に実行する関数を指定します。

ライフライクルは、nuxtServerInitasyncDataに実行されます。

ファイルは「middleware」ディレクトリ内で管理します。

middleware/authenticated.js(参考コード)
export default function ({ store, redirect }) {
  if (!store.state.authenticated) {
    return redirect('/login')
  }
}

middlewareの指定方法は3つあります。

1. プロジェクト全体で指定する

全てのページ遷移で呼び出す場合は、nuxt.config.jsに登録します。

登録方法は、router.middlewareプロパティにファイル名を文字列で指定します。

nuxt.config.js
export default {
  router: {
    middleware: 'authenticated'
    // 複数ファイル指定の場合は配列で
    // middleware: ['authenticated', 'stats']
  }
}

2. 特定のページで指定する

ページ単位で指定する場合は、Vueファイルのmiddlewareプロパティにファイル名を指定します。

レイアウト単位でも指定可能です。

pages/index.vue
<script>
  export default {
    middleware: 'authenticated'
  }
</script>

3 .特定のページで関数を指定する

小さい処理や、使い回さない処理であれば直接関数を書くことができます。

pages/index.vue
<script>
  export default {
    middleware({ store, redirect }) {
      if (!store.state.authenticated) {
        return redirect('/login')
      }
    }
  }
</script>

呼び出し順序

優先度は、

  1. nuxt.config.jsのミドルウェア
  2. レイアウトファイルのミドルウェア
  3. ページファイルのミドルウェア

となります。

参考: ミドルウェア - Nuxt.js

(コラム終わり)

ログイン前のユーザーのリダイレクト処理

それでは、ログイン前のユーザーをリダイレクトする処理を実装します。

「middleware」ディレクトリ内にauthenticator.jsを作成しましょう。

root $ touch front/middleware/authenticator.js

レイアウトにミドルウェアを追加する

ログインのページに使用するレイアウトファイル

  • default.vue
  • loggedIn.vue

middlewareを追加します。

front/layouts/loggedIn.vue
...
<script>
export default {
  // 追加
  middleware: 'authenticator'
}
</script>
front/layouts/default.vue
...
<script>
export default {
  // 追加
  middleware: 'authenticator'
}
</script>

トップページは、レイアウト決定前にログイン判定を行いたいので、index.vuemiddlewareを追加します。

front/pages/index.vue
...
<script>
import homeImg from '~/assets/images/loggedIn/home.png'

export default {
  // middleware 追加
  middleware: 'authenticator',
  layout ({ $auth }) {
    return $auth.loggedIn ? 'loggedIn' : 'welcome'
  },
  ...
}
</script>

トップページの処理

トップページは、ログイン前後の双方で使用するため、個別に処理が必要です。

トップページかつ、Vuexのユーザーが存在しない場合、何もしない処理を追加します。

この処理を追加しないと、ウェルカムページにアクセスできなくなります。

front/middleware/authenticator.js
export default async ({ $auth, store, route, redirect }) => {
  // トップページかつユーザーが存在しない場合、何もしない(layouts/welcome.vue表示のため)
  if (route.name === 'index' && !$auth.isUserPresent()) {
    return false
  }
}

有効期限が切れている時の処理

アクセス時に有効期限が切れている場合

  • Vuexのユーザーが存在する時は

    • ログアウト処理をし、Vuexのユーザーをnullにします。
  • Vuexのユーザーが存在しない時は

    • アクセスしようとしたルートを記憶します。
  • 共通処理として

    • トースターの出力と、
    • ログインページへリダイレクトします。

これらの処理をauthenticator.jsに追加しましょう。

なお、トースターは先の記事で作成します。

front/middleware/authenticator.js
export default async ({ $auth, store, route, redirect }) => {
  // トップページかつユーザーが存在しない場合、何もしない(layouts/welcome.vue表示のため)
  if (route.name === 'index' && !$auth.isUserPresent()) {
    return false
  }

  // 以下、追加

	// トップページでユーザーが存在する場合はここを通過する
  if (!$auth.isAuthenticated()) {
    // 有効期限外の時
    let msg = 'ログインが必要です'

    if ($auth.isUserPresent()) {
      // ログイン中のユーザー
      msg = 'もう一度ログインしてください'
      await $auth.logout()
    } else {
      // ログイン前ユーザー
      store.dispatch('getRememberRoute', route)
    }

    // TODO トースター出力
    console.log(msg)
    return redirect('/login')
  }
}

認証エラーを追加する

実装上ありえないですが、有効期限内にもかかわらずユーザーが存在しない場合の処理も行います。

これは、有効期限が偽造されたケースが考えられるので、認証エラーを吐くようにします。

front/middleware/authenticator.js
export default async ({ $auth, store, route, redirect }) => {
  ...
  if (!$auth.isAuthenticated()) {
    ...
    return redirect('/login')
   // 追加
  } else if (!$auth.isUserPresent()) {
    // 有効期限内でユーザーが存在しない場合
    return $auth.unauthError()
  }
}

unauthError()auth.jsの一番下に追加します。

front/plugins/auth.js
...
class Authentication {
  constructor (ctx) {
    this.store = ctx.store
    this.$axios = ctx.$axios
    // 追加
    this.error = ctx.error
  }
  ...

  // ログアウト業務
  logout () {
    ...
  }

  // 追加
  // 認証エラー処理
  unauthError () {
    this.removeStorage()
    throw this.error({ statusCode: 401, message: 'Unauthorized' })
  }
}

// error 追加
export default ({ store, $axios, error }, inject) => {
  inject('auth', new Authentication({ store, $axios, error }))
}
  • throw(スロー) … 処理をストップする。エラー出力以後の処理は実行されません。

ログイン前のルートを記憶する

index.jsに、ログイン前のユーザーがアクセスしようとしたルートを記憶する

  • アクション
  • ミューテーション
  • ステート

をそれぞれ追加しましょう。

front/store/index.js
export const state = () => ({
  ...
  projects: [
    ...
  ],
  // 追加
  rememberRoute: {
    name: 'index',
    params: {}
  }
})

export const getters = {}

export const mutations = {
  ...
  setCurrentUser (state, payload) {
    ...
  },
  // 追加
  setRememberRoute (state, payload) {
    state.rememberRoute = payload
  }
}

export const actions = {
  ...
  // 現在のユーザーを設定する
  getCurrentUser ({ commit }, user) {
    ...
  },
  // 追加
  // ログイン前にアクセスしたルートを記憶する
  getRememberRoute ({ commit }, route) {
    route = route || { name: 'index', params: {} }
    commit('setRememberRoute', { name: route.name, params: route.params })
  }
}

ログインページにリダイレクト処理を追加する

login.vueに、ログイン後のリダイレクト処理を追加します。

リダイレクト先は、上記で追加した記憶ルートにリダイレクトします。

front/pages/login.vue
...
<script>
export default {
  ...
  methods: {
    ...
    // ログイン成功
    authSuccessful (response) {
      this.$auth.login(response)
      // 追加
      this.$router.push(this.$store.state.rememberRoute)
    },
    ...
  }
}
</script>

リダイレクトエラーを回避する

今現在

アクセスすると、404エラーが出るよう設定されています。

ログイン後のリダイレクト直後に、いきなり404エラーが出るとユーザーにストレスがかかります。

そこで、トップページへリダイレクトするよう変更を行います。

front/pages/account.vue
...
<script>
export default {
  layout: 'loggedIn',
  // 追加
  middleware ({ route, redirect }) {
    if (route.name === 'account') { return redirect('/') }
  }
  // 削除
  // validate ({ route }) {
  //   return route.name !== 'account'
  // }
}
</script>
front/pages/project.vue
...
<script>
export default {
  // route, redirect追加
  async middleware ({ params, store, route, redirect }) {
    // 追加
    if (route.name === 'project') { return redirect('/') }
    return await store.dispatch('getCurrentProject', params)
  },
  ...
}
</script>

認証前のリダイレクト処理を確認しよう

リダイレクト処理の確認事項を整理します。

ログイン前のユーザー

  • コンソールに「ログインが必要です」が出力されているか
  • 記憶ルートにリダイレクトされているか
  • 記憶ルートが/account/settingsの場合、ホームにリダイレクトされているか

コンソールに「ログインが必要です」が出力されているか

ログイン前の状態で、http://localhost:8080/project/2/dashboard にアクセスしてみましょう。

このルートには、レイアウトファイルdefault.vueのミドルウェアが発動します。

2020-10-13 19-06-39

ログインページにリダイレクトされ、コンソールも期待通りです。

記憶ルートにリダイレクトされているか

これでルートが記憶されたか確認します。

そのままログインしてみましょう。

2020-10-13 19-09-29

記憶ルートにリダイレクトされました。

記憶ルートがaccount、projectの場合、ホームにリダイレクトされているか

それでは、

それぞれにアクセスし、ホームにリダイレクトされていることも確認しておきましょう。

ログイン後のユーザー

ログイン後のユーザーの確認事項は

  • コンソールに「もう一度ログインしてください」が出力されているか

  • ログアウトが成功しているか

この2点です。

ログアウトとは、Cookie、ストレージ、ユーザーが削除されている状態です。

コンソールに「もう一度ログインしてください」が出力されているか

ログイン状態で、ローカルストレージの有効期限を削除します。

2020-10-10 20-01-40

そのまま、リロードせずにどこかにページ遷移してください。

2020-10-13 20-09-27

期待通りに動いていますね。

ログアウトが成功しているか

  • ローカルストレージの有効期限
  • Cookieのトークン

が削除されていることを確認してください。

有効期限内でユーザーが存在しない場合

特殊ですが、有効期限内でユーザーが存在しない場合もテストしておきましょう。

authenticator.rbに、強制的にユーザーをnullにするテストコードを追加します。

front/middleware/authenticator.js
export default async ({ $auth, store, route, redirect }) => {
  ...

  // 追加
  store.dispatch('getCurrentUser', null)

	// トップページでユーザーが存在する場合はここを通過する
  if (!$auth.isAuthenticated()) {
    ...
  } ...
}

もう一度ログインすると、認証エラーが発生しました。

2020-10-13 20-30-41

認証エラーが出た瞬間はCookieのトークンは残っていますが、先で実装するエラーページでCookieを削除します。

確認が済んだらテストコードは削除しましょう。

front/middleware/authenticator.js
export default async ({ $auth, store, route, redirect }) => {
	...
  // store.dispatch('getCurrentUser', null) 削除
	...
}

ログインのユーザーリダイレクト処理は以上です。

ログイン後のユーザーをリダイレクトする

ログインのユーザーは、

  • ログインページ
  • 会員登録ページ

にアクセスすると、トップページにリダイレクトする処理を追加します。

「middleware」ディレクトリ内にloggedInIsRedirects.jsを作成しましょう。

root $ touch front/middleware/loggedInIsRedirects.js

ルーター名がsignuploginの時にリダイレクト処理を行います。

front/middleware/loggedInIsRedirects.js
export default ({ $auth, route, redirect }) => {
  // ログイン後ユーザーのリダイレクト処理
  const loggedInUserNotAccess = ['signup', 'login']
  if ($auth.loggedIn && loggedInUserNotAccess.includes(route.name)) {
    return redirect('/')
  }
}

レイアウトファイルに登録する

会員登録ページとログインページのレイアウトファイルは、beforeLogin.vueです。

middlewareプロパティを追加しましょう。

front/layouts/beforeLogin.vue
<script>
export default {
  // 追加
  middleware: 'loggedInIsRedirects'
}
</script>

ログイン後のリダイレクト処理を確認しよう

ログイン状態で、

にアクセスしてください。

トップページにリダイレクトされたら成功です。

コミットします

今回の実装は以上です。

コミットしておきましょう。

root $ cd front
front $ git add -A && git commit -m "add_redirect_middleware" && cd ..
root $

まとめ

今回はログイン前後のリダイレクト処理を行いました。

これで、ログイン前のユーザーとログイン後のユーザーのアクセス制限が実装できました。

ここまでの実装で、

  • 無効な期限はページ遷移毎に削除されます。
  • 有効な期限を偽造しても、Vuexにユーザーが存在しない限りログイン後ページは表示できません。
  • Cookieにアクセストークンが存在しない場合、リロード毎に有効期限はnuxtClientInit.jsで削除されます。
  • Vuexのユーザーを偽造しても、プロジェクト一覧を取得する際にトークンを持っていないので、Rails側でエラーを吐きます。

よし、だいぶセキュリティが固まってきましたね。

予告

ログイン失敗時のトースターを作成します。

あの「ユーザーが見つかりません」みたいなメッセージですね。

今回作成したauthenticator.jsにもトースターを差し込みます。

お楽しみに。

あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる...。そんな方に向けた単発・短期間メンターサービスを行っています。下のサービスへお進みください。
独学プログラマのサービス
次の記事はこちら
1. このカテゴリーの歩き方 #01
【お知らせ】UdemyでRails × Nuxt.jsの動画を公開することになりました
1. このカテゴリーの歩き方 #02
アプリケーション仕様書
1. このカテゴリーの歩き方 #03
このカテゴリーの歩き方(まずはここをチェック)
1. このカテゴリーの歩き方 #04
(Docker+Rails6+Nuxt.js+PostgreSQL)=>Heroku 環境構築~デプロイまでの手順書
2. Docker入門 #01
Docker for Macをインストールする手順
2. Docker入門 #02
分かるDocker解説。仮想環境・コンテナ・Dockerイメージ・Dockerfileとは何か?
2. Docker入門 #03
分かるDocker解説。DockerComposeとは何か?
3. Dockerを使ったRails+Nuxt.js環境構築 #01
【Docker+Rails6+Nuxt.js】今回作成するアプリの開発環境の全体像を知ろう
3. Dockerを使ったRails+Nuxt.js環境構築 #02
【MacOS】Homebrew経由でGitをインストールする方法
3. Dockerを使ったRails+Nuxt.js環境構築 #03
Rails6を動かすAlpineベースのDockerfileを作成する(AlpineLinuxとは何か)
3. Dockerを使ったRails+Nuxt.js環境構築 #04
Nuxt.jsを動かすAlpineベースのDockerfileを作成する(C.UTF-8とは何か)
3. Dockerを使ったRails+Nuxt.js環境構築 #05
.envファイルを使ったdocker-compose.ymlの環境変数設計
3. Dockerを使ったRails+Nuxt.js環境構築 #06
Rails6・Nuxt.js・PostgreSQLを動かすdocker-compose.ymlファイルを作成する
3. Dockerを使ったRails+Nuxt.js環境構築 #07
docker-compose.ymlを使ってRails6を構築する(PostgreSQLパスワード変更方法)
3. Dockerを使ったRails+Nuxt.js環境構築 #08
docker-compose.ymlを使ってNuxt.jsを構築する
4. 複数プロジェクトのGit管理 #01
複数プロジェクトで行うGit管理の全体像を理解しよう(Gitサブモジュール解説)
4. 複数プロジェクトのGit管理 #02
【Git】既存の子ディレクトリをサブモジュール管理に変更する手順
4. 複数プロジェクトのGit管理 #03
【GitHub】秘密鍵の生成・公開鍵を追加・SSH接続するまでを画像で分かりやすく
4. 複数プロジェクトのGit管理 #04
【GitHub】リモートリポジトリの追加・サブモジュールのリンク設定を行う
5. RailsAPI×Nuxt.js初めてのAPI通信 #01
【Rails6】"Hello" jsonを返すコントローラを作成する
5. RailsAPI×Nuxt.js初めてのAPI通信 #02
【Nxut.js】axiosの初期設定を行う(baseURL・browserBaseURLを解説)
5. RailsAPI×Nuxt.js初めてのAPI通信 #03
【Rails6】Gem rack-corsを導入してCORS設定を行う(オリジン・CORSとは何か)
6. Heroku.ymlを使ったDockerデプロイ #01
デプロイ準備。Herokuへ新規会員登録を行いHerokuCLIをインストールする
6. Heroku.ymlを使ったDockerデプロイ #02
heroku.yml解説編。Docker環境のRails6をHerokuにデプロイする(1/2)
6. Heroku.ymlを使ったDockerデプロイ #03
HerokuCLI-manifestのデプロイ解説編。Docker環境のRails6をHerokuにデプロイする(2/2)
6. Heroku.ymlを使ったDockerデプロイ #04
Dockerfile解説編。Docker環境のNuxt.jsをHerokuにデプロイする(1/2)
6. Heroku.ymlを使ったDockerデプロイ #05
デプロイ完結編。Docker環境のNuxt.jsをHerokuにデプロイする(2/2)
7. モデル開発事前準備 #01
【Rails6】application.rbの初期設定(タイムゾーン・I18n・Zeitwerk)
7. モデル開発事前準備 #02
【Rails6】モデル開発に必要なGemのインストールとHirb.enableの自動化
7. モデル開発事前準備 #03
【Docker+Rails】A server is already running. Check /tmp/pids/server.pidエラーの対応
7. モデル開発事前準備 #04
【Docker】<none>タグのイメージを一括削除する & Rails .gitignoreの編集
8. ユーザーモデル開発 #01
Railsユーザーモデル作成。テーブル設計・ユーザー認証設計を理解する
8. ユーザーモデル開発 #02
Railsユーザーモデルのバリデーション設定(has_secure_password解説)
8. ユーザーモデル開発 #03
Railsバリデーションエラーメッセージの日本語化(ja.yml設定方法)
8. ユーザーモデル開発 #04
EachValidatorクラスのカスタムバリデーション設定(Rails6/lib以下読込)
8. ユーザーモデル開発 #05
Rails環境ごとにSeedデータ切り替えるseeds.rbの書き方
8. ユーザーモデル開発 #06
Rails6から導入された並列テストを理解する
8. ユーザーモデル開発 #07
Railsユーザーモデルバリデーションテスティング(name/email/password)
8. ユーザーモデル開発 #08
Nuxt.jsからRailsのユーザーテーブルを取得しHerokuにデプロイする
9. Nuxt.jsフロント開発事前準備 #01
【Nuxt.js2.13超解説】バージョンアップ手順と6つの新機能+2つの変更点
9. Nuxt.jsフロント開発事前準備 #02
Docker AlpineベースのNode.js上で動くNuxt.jsにVuetifyを導入する
9. Nuxt.jsフロント開発事前準備 #03
VuetifyにカスタムCSSを導入してオリジナルブレイクポイントを作る
9. Nuxt.jsフロント開発事前準備 #04
Nuxt.jsにnuxt-i18nを導入して国際化に対応する
10. ログイン前のレイアウト構築 #01
Nuxt.jsのレイアウト・ページ・コンポーネントの役割を理解しよう
10. ログイン前のレイアウト構築 #02
Nuxt.js ウェルカムページを構成するコンポーネントファイル群を作成しよう(1/4)
10. ログイン前のレイアウト構築 #03
Nuxt.js ウェルカムページにアイキャッチ画像・アプリ名・メニューボタンを表示しよう(2/4)
10. ログイン前のレイアウト構築 #04
Nuxt.js addEventListenerでスクロールを検知しツールバーの色を変化させよう(3/4)
10. ログイン前のレイアウト構築 #05
Nuxt.js ウェルカムページをレスポンシブデザインに対応させよう(4/4)
10. ログイン前のレイアウト構築 #06
Nuxt.js 会員登録ページのレイアウトファイルを作成しよう(1/4)
10. ログイン前のレイアウト構築 #07
Nuxt.js 名前、メール、パスワードのコンポーネントファイルを作成しよう(2/4)
10. ログイン前のレイアウト構築 #08
Nuxt.js 親子コンポーネント間の双方向データバインディングを実装する(3/4)
10. ログイン前のレイアウト構築 #09
Nuxt.js Vuetifyのv-text-fieldを使った会員登録フォームのバリデーション設定(4/4)
10. ログイン前のレイアウト構築 #10
Nuxt.js ログインページ実装とHerokuデプロイまで(router. replaceとpushの違いとは)
11. ログイン後のレイアウト構築 #01
Nuxt.js ログイン後のツールバーを作成しよう(inject解説)
11. ログイン後のレイアウト構築 #02
Nuxt.js アカウントメニューページ・ログアウト機能を実装しよう(nuxt-child解説)
11. ログイン後のレイアウト構築 #03
Nuxt.js ログイン後のトップページにプロジェクト一覧を表示しよう
11. ログイン後のレイアウト構築 #04
Nuxt.js プロジェクトページにVuetifyのナビゲーションドロワーを追加しよう
11. ログイン後のレイアウト構築 #05
Nuxt.js paramsIDからプロジェクトを検索してVuexに保存しよう
12. サーバーサイドのログイン認証 #01
JWTとは何か?(ruby-jwtのインストール)
12. サーバーサイドのログイン認証 #02
【Rails×JWT】ログイン認証解説とJWT初期設定ファイルの作成
12. サーバーサイドのログイン認証 #03
【Rails×JWT】トークン発行とデコードを行うAuthTokenクラスの作成
12. サーバーサイドのログイン認証 #04
【Rails×JWT】 ログイン判定を行うAuthenticatorモジュールの作成
12. サーバーサイドのログイン認証 #05
【Rails×JWT】UserクラスからJWTを扱うTokenizableモジュールの作成
12. サーバーサイドのログイン認証 #06
【Rails×JWT】AuthTokenクラスとAuthenticatorモジュールをテストする
12. サーバーサイドのログイン認証 #07
【Rails×JWT】JWTをCookieに保存するログインコントローラーの実装
12. サーバーサイドのログイン認証 #08
【Rails×JWT】ログインコントローラーのテストとHerokuデプロイ
13. フロントエンドのログイン認証 #01
【Rails×Nuxt.js】クロスオリジン通信でのCookie共有設定
13. フロントエンドのログイン認証 #02
【Nuxt.js】Railsからのログイン成功レスポンスをVuexに保存する
13. フロントエンドのログイン認証 #03
【Nuxt.js】ローカルストレージの有効期限を暗号化する(crypto-js解説)
13. フロントエンドのログイン認証 #04
【Nuxt.js】JWT有効期限内のユーザーをログインしたままにする実装
13. フロントエンドのログイン認証 #05
【Nuxt.js】ログイン前後のリダイレクト処理をミドルウェアで実装する
13. フロントエンドのログイン認証 #06
【Nuxt.js】ログイン失敗時のトースターをグローバルイベントとして作成する
13. フロントエンドのログイン認証 #07
【Nuxt.js】エラーページを作成する
13. フロントエンドのログイン認証 #08
【Rails×Nuxt.js】デモプロジェクトの作成とHerokuデプロイ(ログイン認証完)
14. 本番環境への対応 #01
【Rails×Nuxt.js】SafariのクロスサイトCookie保存拒否に対応する
SPA開発
ブログ構築
小ネタ集