序章

このチュートリアルでは、Authモジュールを使用して、Nuxt.jsアプリに認証を実装します。

このチュートリアルでは、認証にJWTを使用します。

以下は、このチュートリアルで構築するものの簡単なデモです。

Animated gif of the app showing a user signing in

このアプリケーションのソースコードはGitHubにあります。

警告:このチュートリアルのいくつかのパッケージには、既知の脆弱性を持つ依存関係が含まれています。 本番環境では、これらのパッケージをアップグレードするか、代替手段を見つけるか、パッチが適用された修正を含むフォークバージョンを作成することで、これらの問題を解決します。 ただし、チュートリアルの限られたコンテキスト内では、教育的価値をそのまま提供します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

Vue.jsとNuxt.jsにある程度精通していると有益な場合があります。 Nuxt.jsを使い始めた場合は、この投稿を参照できます。

このチュートリアルは、ノードv13.13.0、npm v6.14.4、vue v2.6.11、およびnuxtv2.12.2で検証されました。

ステップ1—サンプルAPIを起動する

自分に最適なフレームワークを自由に使用できます。 ただし、迅速な開発のために、このチュートリアルではAdonisJsで構築されたAPIのクローンを作成します。

APIは以下を利用します:

APIには3つのエンドポイントがあります。

  • /register:ユーザー登録のエンドポイント
  • /login:ユーザーを認証するためのエンドポイント
  • /me:現在認証されているユーザーの詳細を取得するためのエンドポイント。authミドルウェアによって保護されています。つまり、エンドポイントにアクセスするにはユーザーを認証する必要があります。

まず、ターミナルウィンドウで次のコマンドを実行します。

  1. git clone https://github.com/do-community/jwt-auth-api.git

次に、プロジェクトディレクトリに移動します。

  1. cd jwt-auth-api

そして、APIの依存関係をインストールします。

  1. npm install

:インストールを実行すると、実行しているノードのバージョンによっては、sqlite3バージョン4.0.1で問題が発生する場合があります。 changelog を参照して、ご使用の環境との互換性を確認してください。

当初の発行時点では、Nodeの最新バージョンは10でした。 1つのオプションは、Nodeのバージョンを10.20.1にダウングレードすることです(サポートが終了に近づいていることを理解してください)。 次に、npm installを実行します。

2番目のオプションは、package-lock.jsonファイルを削除することです。これにより、システムはノード13までサポートされている4.2.0を検索します。 Nodeのバージョンを13.13.0にダウングレードする必要がある場合もあります。 次に、npm installを実行します。

3番目のオプションは、package.jsonを現在のバージョンのNodeでサポートされているバージョンのsqlite3に変更し、package-lock.jsonを削除して、npm installを実行することです。 ただし、テストの時点では、ノード14以降のサポートを処理するための5.0.0はまだリリースされていません。

非互換性の他の症状には、次のエラーが含まれます:TypeError: Cannot read property 'data' of undefinedおよびError: Cannot find module '[...]/node_modules/sqlite3/lib/binding/[...]/node_sqlite3.node'

次に、.env.exampleの名前を.envに変更します。

  1. mv .env.example .env

そして、APP_KEYを生成します。

  1. npx @adonisjs/cli@4.0.12 key:generate

君は見るべきだ:

  1. Output
    generated: unique APP_KEY

それが完了したら、移行を実行してみましょう。

  1. npx @adonisjs/cli@4.0.12 migration:run

これで、APIを開始できます。

  1. # ensure that you are in the `jwt-auth-api` project directory
  2. npm start

http://127.0.0.1:3333/apiでAPIにアクセスできます。 チュートリアルの残りの期間は、これをターミナルウィンドウで実行したままにします。

ステップ2—Nuxt.jsアプリを作成する

これで、Nuxt.jsアプリを作成できます。 新しいターミナルウィンドウを開き、vue-cliを使用して、Nuxtスターターテンプレートで新しいVueプロジェクトを初期化します。

  1. npx [email protected] init nuxt/starter nuxt-auth

注:テストの時点で、vue-cliは非推奨です。 @vue/cliは、Vueプロジェクト用の現在のコマンドラインツールです。 また、@vue/cli-initは、レガシーvue-cliプロジェクトに推奨されるアプローチです。 ただし、create-nuxt-appは、最新のNuxtプロジェクトに推奨されるアプローチです。

次に、プロジェクトディレクトリに移動する必要があります。

  1. cd nuxt-auth

そして、依存関係をインストールします。

npm install

次に、アプリを起動できます。

  1. npm run dev

アプリはhttp://localhost:3000で実行されている必要があります。 Webブラウザーでアプリケーションを表示して、vue-cliによって作成されたデフォルトのVueアプリケーションを表示できます。

ステップ3—必要なNuxt.jsモジュールをインストールする

それでは、アプリに必要なNuxt.jsモジュールをインストールしましょう。 authモジュールは内部でAxiosを使用するため、NuxtAuthモジュールNuxtAxiosモジュールを使用します。

  1. # ensure that you are in the `nuxt-auth` project directory
  2. npm install @nuxtjs/auth@4.5.1 @nuxtjs/axios@5.3.1 --save

それが完了したら、nuxt.config.jsを開きます。

  1. nano nuxt.config.js

以下のコードをnuxt.config.jsに追加します。

nuxt.config.js
module.exports = {
  // ...

  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth'
  ],
}

注:この時点で、Nuxtの新しいバージョンでエラーEnable vuex store by creating 'store/index.js'が発生する可能性があります。 このエラーは、ストアディレクトリに空のindex.jsファイルを追加することで解決できます。

次に、モジュールを設定する必要があります。 以下のコードをnuxt.config.jsに貼り付けます。

nuxt.config.js
module.exports = {
  // ...

  axios: {
    baseURL: 'http://127.0.0.1:3333/api'
  },

  auth: {
    strategies: {
      local: {
        endpoints: {
          login: { url: 'login', method: 'post', propertyName: 'data.token' },
          user: { url: 'me', method: 'get', propertyName: 'data' },
          logout: false
        }
      }
    }
  }
}

ここでは、Axiosがリクエストを行うときに使用するベースURLを設定します。 この例では、前に設定したサンプルAPIを参照しています。

次に、APIの認証エンドポイントに対応するlocal戦略の認証エンドポイントを定義します。

  • 認証が成功すると、トークンはdataオブジェクト内のtokenオブジェクトとして応答で使用可能になります。
  • 同様に、/meエンドポイントからの応答は、dataオブジェクト内にあります。
  • 最後に、APIにはログアウト用のエンドポイントがないため、logoutfalseに設定します。 ユーザーがログアウトしたときに、localStorageからトークンを削除するだけです。

ステップ4—ナビゲーションバーコンポーネントを作成する

アプリのスタイルを設定するには、ブルマを利用できます。

nuxt.config.jsを開き、headオブジェクト内にあるlinkオブジェクト内に以下のコードを貼り付けます。

nuxt.config.js
module.exports = {
  // ...
  head: {
    // ...
    link [
      // ...
      {
        rel: 'stylesheet',
        href: 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css'
      }
    ]
  },
  // ...
}

それでは、Navbarコンポーネントを作成しましょう。

  1. nano components/Navbar.vue

そして、次のコードを追加します。

コンポーネント/Navbar.vue
<template>
  <nav class="navbar is-light">
    <div class="container">
      <div class="navbar-brand">
        <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
        <button class="button navbar-burger">
          <span></span>
          <span></span>
          <span></span>
        </button>
      </div>
      <div class="navbar-menu">
        <div class="navbar-end">
          <div class="navbar-item has-dropdown is-hoverable">
            <a class="navbar-link">
              My Account
            </a>
            <div class="navbar-dropdown">
              <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
              <hr class="navbar-divider"/>
              <a class="navbar-item">Logout</a>
            </div>
          </div>
          <template>
            <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
            <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
          </template>
        </div>
      </div>
    </div>
  </nav>
</template>

Navbarコンポーネントには、loginregisterprofile、およびlogoutへのリンクが含まれています。

次に、Navbarコンポーネントを使用するようにデフォルトのレイアウトを更新しましょう。

default.vueを開きます:

  1. nano layouts/default.vue

そして、コンテンツを次のように置き換えます。

layouts / default.vue
<template>
  <div>
    <Navbar/>
    <nuxt/>
  </div>
</template>

<script>
import Navbar from '~/components/Navbar'

export default {
  components: {
    Navbar
  }
}
</script>

また、ホームページを更新しましょう。

index.vueを開きます:

  1. nano pages/index.vue

そして、コンテンツを次のように置き換えます。

pages / index.vue
<template>
  <section class="section">
    <div class="container">
      <h1 class="title">Nuxt Auth</h1>
    </div>
  </section>
</template>

この時点で、ナビゲーションリンク付きのヘッダーバーを備えた"Nuxt Auth"のタイトルを表示するアプリケーションが必要です。

App page with title and header bar

ステップ5—ユーザー登録の処理

pagesディレクトリ内に、新しいregister.vueファイルを作成します。

  1. nano pages/register.vue

そして、次のコードを追加します。

pages / register.vue
<template>
  <section class="section">
    <div class="container">
      <div class="columns">
        <div class="column is-4 is-offset-4">
          <h2 class="title has-text-centered">Register!</h2>

          <Notification :message="error" v-if="error"/>

          <form method="post" @submit.prevent="register">
            <div class="field">
              <label class="label">Username</label>
              <div class="control">
                <input
                  type="text"
                  class="input"
                  name="username"
                  v-model="username"
                  required 
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Email</label>
              <div class="control">
                <input
                  type="email"
                  class="input"
                  name="email"
                  v-model="email"
                  required
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Password</label>
              <div class="control">
                <input
                  type="password"
                  class="input"
                  name="password"
                  v-model="password"
                  required
                />
              </div>
            </div>
            <div class="control">
              <button type="submit" class="button is-dark is-fullwidth">Register</button>
            </div>
          </form>

          <div class="has-text-centered" style="margin-top: 20px">
            Already got an account? <nuxt-link to="/login">Login</nuxt-link>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>

<script>
import Notification from '~/components/Notification'

export default {
  components: {
    Notification,
  },

  data() {
    return {
      username: '',
      email: '',
      password: '',
      error: null
    }
  },

  methods: {
    async register() {
      try {
        await this.$axios.post('register', {
          username: this.username,
          email: this.email,
          password: this.password
        })

        await this.$auth.loginWith('local', {
          data: {
          email: this.email,
          password: this.password
          },
        })

        this.$router.push('/')
      } catch (e) {
        this.error = e.response.data.message
      }
    }
  }
}
</script>

これには、usernameemail、およびpasswordの3つのフィールドを持つフォームが含まれています。 各フィールドは、コンポーネントの対応するデータにバインドされます。 フォームが送信されると、registerメソッドが呼び出されます。 Axiosモジュールを使用して、/registerエンドポイントにPOSTリクエストを行い、ユーザーデータを渡します。 登録が成功した場合は、AuthモジュールのloginWith()を使用し、local戦略を使用して、ユーザーデータを渡してユーザーをログインさせます。 次に、ユーザーをホームページにリダイレクトします。 登録中にエラーが発生した場合は、API応答から取得したエラーメッセージとしてerrorデータを設定します。

エラーが発生した場合、エラーメッセージは通知コンポーネントによって表示されます。

components内に新しいNotification.vueファイルを作成します。

  1. nano components/Notifaction.vue

そして、その中に以下のコードを貼り付けます。

components / Notification.vue
<template>
  <div class="notification is-danger">
    {{ message }}
  </div>
</template>

<script>
export default {
  name: 'Notification',
  props: ['message']
}
</script>

通知コンポーネントは、エラーメッセージであるmessage小道具を受け入れます。

これで、ユーザー登録をテストできます。

Register page with Username, Email, and Password fields

Register page but with a notification message to the user that there was an error

ステップ6—ログインおよびログアウトしたLoggedUsersの処理

登録が成功すると、ユーザーはログインする必要がありますが、現在、アプリがユーザーがログインしているかどうかを知る方法はありません。 それでは、Navbarコンポーネントを更新し、計算されたプロパティをいくつか追加して、これを修正しましょう。

その前に、まずstoreディレクトリ内にindex.jsファイルを作成してVuexストアをアクティブ化しましょう。 Authモジュールは、ユーザー認証ステータスとVuex状態内のユーザー詳細をauthオブジェクトに保存します。 したがって、ユーザーがthis.$store.state.auth.loggedInでログインしているかどうかを確認できます。これにより、trueまたはfalseが返されます。 同様に、this.$store.state.auth.userを使用してユーザーの詳細を取得できます。これは、ユーザーがログインしていない場合はnullになります。

注: this.$auth.loggedInthis.$auth.userをそれぞれ使用して、Authモジュールを使用してユーザー認証ステータスとユーザー詳細に直接アクセスすることもできます。

計算されたプロパティをアプリの複数の場所で使用したい場合があるので、ストアゲッターを作成しましょう。

index.jsを開きます:

  1. nano store/index.js

そして、その中に以下のコードを貼り付けます。

store / index.js
export const getters = {
  isAuthenticated(state) {
    return state.auth.loggedIn
  },

  loggedInUser(state) {
    return state.auth.user
  }
}

ここでは、2つのゲッターを作成します。 1つ目(isAuthenticated)はユーザーの認証ステータスを返し、2つ目(loggedInUser)は詳細またはログインしたユーザーを返します。

次に、ゲッターを利用するようにNavbarコンポーネントを更新しましょう。 components/Navbar.vueの内容を次のように置き換えます。

コンポーネント/Navbar.vue
<template>
  <nav class="navbar is-light">
    <div class="container">
      <div class="navbar-brand">
        <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
        <button class="button navbar-burger">
          <span></span>
          <span></span>
          <span></span>
        </button>
      </div>
      <div class="navbar-menu">
        <div class="navbar-end">
          <div class="navbar-item has-dropdown is-hoverable" v-if="isAuthenticated">
            <a class="navbar-link">
              {{ loggedInUser.username }}
            </a>
            <div class="navbar-dropdown">
              <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
              <hr class="navbar-divider"/>
              <a class="navbar-item">Logout</a>
            </div>
          </div>
          <template v-else>
            <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
            <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
          </template>
        </div>
      </div>
    </div>
  </nav>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['isAuthenticated', 'loggedInUser'])
  }
}
</script>

スプレッド演算子(...)を使用して計算されたプロパティを作成し、mapGettersからゲッターを抽出します。 次に、isAuthenticatedを使用して、ユーザーがログインしているかどうかに応じて、ユーザーメニューまたはloginまたはregisterへのリンクを表示します。 また、loggedInUserを使用して、認証されたユーザーのユーザー名を表示します。

ここで、アプリを更新すると、次のようなものが表示されます。

App page with the user's username in the header

ステップ7—ユーザーログインの処理

それでは、リピーターがログインできるようにしましょう。

pagesディレクトリ内に新しいlogin.vueファイルを作成します。

nano pages/login.vue

そして、その中に以下のコードを貼り付けます。

pages / login.vue
<template>
  <section class="section">
    <div class="container">
      <div class="columns">
        <div class="column is-4 is-offset-4">
          <h2 class="title has-text-centered">Welcome back!</h2>

          <Notification :message="error" v-if="error"/>

          <form method="post" @submit.prevent="login">
            <div class="field">
              <label class="label">Email</label>
              <div class="control">
                <input
                  type="email"
                  class="input"
                  name="email"
                  v-model="email"
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Password</label>
              <div class="control">
                <input
                  type="password"
                  class="input"
                  name="password"
                  v-model="password"
                />
              </div>
            </div>
            <div class="control">
              <button type="submit" class="button is-dark is-fullwidth">Log In</button>
            </div>
          </form>
          <div class="has-text-centered" style="margin-top: 20px">
            <p>
              Don't have an account? <nuxt-link to="/register">Register</nuxt-link>
            </p>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>

<script>
import Notification from '~/components/Notification'

export default {
  components: {
    Notification,
  },

  data() {
    return {
      email: '',
      password: '',
      error: null
    }
  },

  methods: {
    async login() {
      try {
        await this.$auth.loginWith('local', {
          data: {
          email: this.email,
          password: this.password
          }
        })

        this.$router.push('/')
      } catch (e) {
        this.error = e.response.data.message
      }
    }
  }
}
</script>

これは、registerページと非常によく似ています。 フォームには、emailpasswordの2つのフィールドが含まれています。 フォームが送信されると、loginメソッドが呼び出されます。 AuthモジュールloginWith()を使用し、ユーザーデータを渡すことで、ユーザーをログインします。 認証が成功した場合は、ユーザーをホームページにリダイレクトします。 それ以外の場合は、errorをAPI応答から取得したエラーメッセージに設定します。 ここでも、以前の通知コンポーネントを使用してエラーメッセージを表示しています。

App Welcome Back page containing two fields: email and password

ステップ8—ユーザープロファイルの表示

ログインしたユーザーが自分のプロファイルを表示できるようにしましょう。

pagesディレクトリ内に新しいprofile.vueファイルを作成します。

  1. nano pages/profile.vue

そして、その中に以下のコードを貼り付けます。

pages / profile.vue
<template>
  <section class="section">
    <div class="container">
      <h2 class="title">My Profile</h2>
      <div class="content">
        <p>
          <strong>Username:</strong>
          {{ loggedInUser.username }}
        </p>
        <p>
          <strong>Email:</strong>
          {{ loggedInUser.email }}
        </p>
      </div>
    </div>
  </section>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['loggedInUser'])
  }
}
</script>

以前のloggedInUserゲッターを使用してユーザーの詳細を表示していることに注目してください。

マイプロファイルリンクをクリックすると、マイプロファイルページが表示されます。

My Profile page displaying username and email

ステップ9—ユーザーのログアウト

Navbarコンポーネント内のログアウトリンクを更新します。

Navbar.vueを開きます:

  1. nano components/Navbar.vue

@click="logout"を使用するようにログアウトリンクを変更します。

コンポーネント/Navbar.vue
// ...
<div class="navbar-dropdown">
  <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
  <hr class="navbar-divider"/>
  <a class="navbar-item"  @click="logout">Logout</a>
</div>
// ...

ログアウトリンクをクリックすると、logoutメソッドがトリガーされます。

次に、Navbarコンポーネントのスクリプトセクション内にlogoutメソッドを追加しましょう。

コンポーネント/Navbar.vue
// ...

export default {
  // ...
  methods: {
    async logout() {
      await this.$auth.logout();
    },
  },
}

Authモジュールのlogout()を呼び出します。 これにより、ユーザーのトークンがlocalstorageから削除され、ユーザーがホームページにリダイレクトされます。

ステップ10—プロファイルページを制限する

現在のところ、誰でもprofileページにアクセスできます。 また、ユーザーがログインしていない場合は、エラーが発生します。

TypeError on app page

これを修正するには、プロファイルページをログインしているユーザーのみに制限する必要があります。 幸いなことに、Authモジュールを使用してそれを実現できます。 Authモジュールには、このシナリオで使用できるauthミドルウェアが付属しています。

それでは、authミドルウェアをprofileページに追加しましょう。 scriptセクションを次のように更新します。

pages / profile.vue
// ...

export default {
  middleware: 'auth',
  // ...
}

これで、ログインしていないユーザーがprofileページにアクセスしようとすると、ユーザーはloginページにリダイレクトされます。

ステップ11—ゲストミドルウェアの作成

繰り返しになりますが、ログインしているユーザーであっても、ログインページと登録ページにアクセスできます。 これを修正する1つの方法は、ログインページと登録ページをログインしていないユーザーのみに制限することです。 これを行うには、ゲストミドルウェアを作成します。

middlewareディレクトリ内に、新しいguest.jsファイルを作成します。

  1. nano middleware/guest.js

そして、その中に以下のコードを貼り付けます。

ミドルウェア/guest.js
export default function ({ store, redirect }) {
  if (store.state.auth.loggedIn) {
    return redirect('/')
  }
}

ミドルウェアは、最初の引数としてコンテキストを受け入れます。 したがって、コンテキストからstoreredirectを抽出します。 次に、ユーザーがログインしているかどうかを確認してから、ユーザーをホームページにリダイレクトします。 それ以外の場合は、リクエストの通常の実行を許可します。

次に、このミドルウェアを活用しましょう。 loginregisterの両方のscriptセクションを次のように更新します。

pages/login.vueおよびpages/register.vue
// ...

export default {
  middleware: 'guest',
  // ...
}

これで、すべてが期待どおりに機能します。

結論

このチュートリアルでは、Authモジュールを使用してNuxt.jsアプリケーションに認証を実装する方法を説明しました。 また、ミドルウェアを利用して認証フローをスムーズに保つ方法も学びました。

Authモジュールの詳細については、docsをご覧ください。

Vue.jsの詳細については、Vue.jsトピックページで演習とプログラミングプロジェクトを確認してください。