Nuxt.jsアプリで認証を実装する方法
序章
このチュートリアルでは、Auth
モジュールを使用して、Nuxt.jsアプリに認証を実装します。
このチュートリアルでは、認証にJWT
を使用します。
以下は、このチュートリアルで構築するものの簡単なデモです。
このアプリケーションのソースコードはGitHubにあります。
警告:このチュートリアルのいくつかのパッケージには、既知の脆弱性を持つ依存関係が含まれています。 本番環境では、これらのパッケージをアップグレードするか、代替手段を見つけるか、パッチが適用された修正を含むフォークバージョンを作成することで、これらの問題を解決します。 ただし、チュートリアルの限られたコンテキスト内では、そのままの教育的価値を提供します。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Node.jsはローカルにインストールされます。これは、Node.jsのインストール方法とローカル開発環境の作成に従って実行できます。
- APIのクローンを作成するには、オプションで有効なGitのインストールが必要です。Gitの使用開始を参照してください。
Vue.jsとNuxt.jsにある程度精通していると有益な場合があります。 Nuxt.jsを使い始めた場合は、この投稿を参照できます。
このチュートリアルは、ノードv13.13.0、npm v6.14.4、vue
v2.6.11、およびnuxt
v2.12.2で検証されました。
ステップ1—サンプルAPIを起動する
自分に最適なフレームワークを自由に使用できます。 ただし、迅速な開発のために、このチュートリアルではAdonisJsで構築されたAPIのクローンを作成します。
APIは以下を利用します:
- 認証用のJWT( JSON Web Tokens )
- SQLite
- CORSが有効
APIには3つのエンドポイントがあります。
/register
:ユーザー登録のエンドポイント/login
:ユーザーを認証するためのエンドポイント/me
:現在認証されているユーザーの詳細を取得するためのエンドポイント。auth
ミドルウェアによって保護されています。つまり、エンドポイントにアクセスするにはユーザーを認証する必要があります。
まず、ターミナルウィンドウで次のコマンドを実行します。
git clone https://github.com/do-community/jwt-auth-api.git
次に、プロジェクトディレクトリに移動します。
cd jwt-auth-api
そして、APIの依存関係をインストールします。
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
に変更します。
mv .env.example .env
そして、APP_KEY
を生成します。
npx @adonisjs/[email protected] key:generate
君は見るべきだ:
Outputgenerated: unique APP_KEY
それが完了したら、移行を実行してみましょう。
npx @adonisjs/[email protected] migration:run
これで、APIを開始できます。
# ensure that you are in the `jwt-auth-api` project directory npm start
http://127.0.0.1:3333/api
でAPIにアクセスできます。 チュートリアルの残りの期間は、これをターミナルウィンドウで実行したままにします。
ステップ2—Nuxt.jsアプリを作成する
これで、Nuxt.jsアプリを作成できます。 新しいターミナルウィンドウを開き、vue-cli
を使用して、Nuxtスターターテンプレートで新しいVueプロジェクトを初期化します。
npx [email protected] init nuxt/starter nuxt-auth
注:テストの時点で、vue-cli
は非推奨です。 @vue/cli
は、Vueプロジェクト用の現在のコマンドラインツールです。 また、@vue/cli-init
は、レガシーvue-cli
プロジェクトに推奨されるアプローチです。 ただし、create-nuxt-app
は、最新のNuxtプロジェクトに推奨されるアプローチです。
次に、プロジェクトディレクトリに移動する必要があります。
cd nuxt-auth
そして、依存関係をインストールします。
npm install
次に、アプリを起動できます。
npm run dev
アプリはhttp://localhost:3000
で実行されている必要があります。 Webブラウザーでアプリケーションを表示して、vue-cli
によって作成されたデフォルトのVueアプリケーションを表示できます。
ステップ3—必要なNuxt.jsモジュールをインストールする
それでは、アプリに必要なNuxt.jsモジュールをインストールしましょう。 auth
モジュールは内部でAxiosを使用するため、NuxtAuthモジュールとNuxtAxiosモジュールを使用します。
# ensure that you are in the `nuxt-auth` project directory npm install @nuxtjs/[email protected] @nuxtjs/[email protected] --save
それが完了したら、nuxt.config.js
を開きます。
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にはログアウト用のエンドポイントがないため、
logout
をfalse
に設定します。 ユーザーがログアウトすると、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コンポーネントを作成しましょう。
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コンポーネントには、login
、register
、profile
、およびlogout
へのリンクが含まれています。
次に、Navbar
コンポーネントを使用するようにデフォルトのレイアウトを更新しましょう。
default.vue
を開きます:
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
を開きます:
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"
のタイトルを表示するアプリケーションが必要です。
ステップ5—ユーザー登録の処理
pages
ディレクトリ内に、新しいregister.vue
ファイルを作成します。
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>
これには、username
、email
、およびpassword
の3つのフィールドを持つフォームが含まれています。 各フィールドは、コンポーネントの対応するデータにバインドされます。 フォームが送信されると、register
メソッドが呼び出されます。 Axiosモジュールを使用して、/register
エンドポイントに投稿リクエストを行い、ユーザーデータを渡します。 登録が成功した場合は、AuthモジュールのloginWith()
を使用し、local
戦略を使用して、ユーザーデータを渡してユーザーをログインさせます。 次に、ユーザーをホームページにリダイレクトします。 登録中にエラーが発生した場合は、API応答から取得したエラーメッセージとしてerror
データを設定します。
エラーが発生した場合、エラーメッセージは通知コンポーネントによって表示されます。
components
内に新しいNotification.vue
ファイルを作成します。
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
小道具を受け入れます。
これで、ユーザー登録をテストできます。
ステップ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.loggedIn
とthis.$auth.user
をそれぞれ使用して、Authモジュールを使用してユーザー認証ステータスとユーザー詳細に直接アクセスすることもできます。
計算されたプロパティをアプリの複数の場所で使用したい場合があるので、ストアゲッターを作成しましょう。
index.js
を開きます:
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
を使用して、認証されたユーザーのユーザー名を表示します。
ここで、アプリを更新すると、次のようなものが表示されます。
ステップ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
ページと非常によく似ています。 フォームには、email
とpassword
の2つのフィールドが含まれています。 フォームが送信されると、login
メソッドが呼び出されます。 AuthモジュールloginWith()
を使用し、ユーザーデータを渡すことで、ユーザーをログインします。 認証が成功した場合は、ユーザーをホームページにリダイレクトします。 それ以外の場合は、error
をAPI応答から取得したエラーメッセージに設定します。 ここでも、以前の通知コンポーネントを使用してエラーメッセージを表示しています。
ステップ8—ユーザープロファイルの表示
ログインしたユーザーが自分のプロファイルを表示できるようにしましょう。
pages
ディレクトリ内に新しいprofile.vue
ファイルを作成します。
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
ゲッターを使用してユーザーの詳細を表示していることに注目してください。
マイプロファイルリンクをクリックすると、マイプロファイルページが表示されます。
ステップ9—ユーザーのログアウト
Navbarコンポーネント内のログアウトリンクを更新します。
Navbar.vue
を開きます:
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
ページにアクセスできます。 また、ユーザーがログインしていない場合は、エラーが発生します。
これを修正するには、プロファイルページをログインしているユーザーのみに制限する必要があります。 幸いなことに、Authモジュールを使用してそれを実現できます。 Authモジュールには、このシナリオで使用できるauth
ミドルウェアが付属しています。
それでは、auth
ミドルウェアをprofile
ページに追加しましょう。 script
セクションを次のように更新します。
pages / profile.vue
// ... export default { middleware: 'auth', // ... }
これで、ログインしていないユーザーがprofile
ページにアクセスしようとすると、ユーザーはlogin
ページにリダイレクトされます。
ステップ11—ゲストミドルウェアの作成
繰り返しになりますが、ログインしているユーザーであっても、ログインページと登録ページにアクセスできます。 これを修正する1つの方法は、ログインページと登録ページをログインしていないユーザーのみに制限することです。 これを行うには、ゲストミドルウェアを作成します。
middleware
ディレクトリ内に、新しいguest.js
ファイルを作成します。
nano middleware/guest.js
そして、その中に以下のコードを貼り付けます。
ミドルウェア/guest.js
export default function ({ store, redirect }) { if (store.state.auth.loggedIn) { return redirect('/') } }
ミドルウェアは、最初の引数としてコンテキストを受け入れます。 したがって、コンテキストからstore
とredirect
を抽出します。 次に、ユーザーがログインしているかどうかを確認してから、ユーザーをホームページにリダイレクトします。 それ以外の場合は、リクエストの通常の実行を許可します。
次に、このミドルウェアを活用しましょう。 login
とregister
の両方のscript
セクションを次のように更新します。
pages/login.vueおよびpages/register.vue
// ... export default { middleware: 'guest', // ... }
これで、すべてが期待どおりに機能します。
結論
このチュートリアルでは、Authモジュールを使用してNuxt.jsアプリケーションに認証を実装する方法を説明しました。 また、ミドルウェアを利用して認証フローをスムーズに保つ方法も学びました。
Authモジュールの詳細については、docsをご覧ください。
Vue.jsの詳細については、Vue.jsトピックページで演習とプログラミングプロジェクトを確認してください。