認証ルート

Nuxt.js を使った認証ルートの例

ソースコード

ドキュメント

Nuxt.js を使うと認証が必要なルートを簡単に作成できます。

express とセッションを使う

アプリケーションにセッション機能を追加するために expressexpress-session を使います。そのために Nuxt.js をプログラムで使う必要があります。

まず依存パッケージをインストールします:

yarn add express express-session body-parser whatwg-fetch

whatwg-fetch については後ほど述べます。

それから server.js ファイルを作成します:

const { Nuxt, Builder } = require('nuxt')
const bodyParser = require('body-parser')
const session = require('express-session')
const app = require('express')()

// `req.body` へアクセスするための Body parser
app.use(bodyParser.json())

// `req.session` を作るためのセッション
app.use(session({
  secret: 'super-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: { maxAge: 60000 }
}))

// POST `/api/login` でログイン、`req.session.authUser` に追加
app.post('/api/login', function (req, res) {
  if (req.body.username === 'demo' && req.body.password === 'demo') {
    req.session.authUser = { username: 'demo' }
    return res.json({ username: 'demo' })
  }
  res.status(401).json({ error: 'Bad credentials' })
})

// POST `/api/logout` でログアウト、`req.session` から削除
app.post('/api/logout', function (req, res) {
  delete req.session.authUser
  res.json({ ok: true })
})

// オプションと併せて Nuxt.js をインスタンス化
const isProd = process.env.NODE_ENV === 'production'
const nuxt = new Nuxt({ dev: !isProd })
// No build in production
if (!isProd) {
  const builder = new Builder(nuxt)
  builder.build()
}
app.use(nuxt.render)
app.listen(3000)
console.log('Server is listening on http://localhost:3000')

また package.json scripts を更新します:

// ...
"scripts": {
  "dev": "node server.js",
  "build": "nuxt build",
  "start": "cross-env NODE_ENV=production node server.js"
}
// ...

情報: 上の例を動かすためには npm install --save-dev cross-env を実行する必要があります。もし Windows で開発しているの でない ならば、start スクリプトから cross-env を削除して、直接 NODE_ENV をセットすることもできます。

ストアを使う

アプリケーションが、ユーザーが認証されているか否かを ページをまたいで 知るためには、グローバルなステート(状態)が必要です。

Nuxt.js が Vuex を使うよう store/index.js ファイルを作成します:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// `window.fetch()` 用のポリフィル
require('whatwg-fetch')

const store = () => new Vuex.Store({

  state: {
    authUser: null
  },

  mutations: {
    SET_USER: function (state, user) {
      state.authUser = user
    }
  },

  actions: {
    // ...
  }

})

export default store
  1. VueVuex(Nuxt.js に含まれる)をインポートし、コンポーネントで $store を使うために Vuex を使うことを Vue に伝えます。
  2. すべてのブラウザで fetch() メソッドをポリフィルするために、require('whatwg-fetch') が必要です(fetch リポジトリを参照してください)。
  3. 接続したユーザーに state.authUser を設定する SET_USER ミューテーションを作成します。
  4. メインアプリケーションにインジェクションできるように、ストアインスタンスを Nuxt.js にエクスポートします。

nuxtServerInit() アクション

Nuxt.js は nuxtServerInit と呼ばれる特定のアクションを、引数でコンテキストを渡して呼び出します。したがって、アプリケーションがロードされたとき、サーバーから取得できるデータがストアに既には入れられています。

store/index.js 内に nuxtServerInit アクションを追加できます:

nuxtServerInit ({ commit }, { req }) {
  if (req.session && req.session.authUser) {
    commit('SET_USER', req.session.authUser)
  }
}

data メソッドを非同期にするために、Nuxt.js はいくつか異なる方法を用意しています。よく知っている方法を選んでください:

  1. Promise を返却する。Nuxt.js はコンポーネントをレンダリングする前に、プロミスが解決されるのを待ちます。
  2. async/await プロポーサル を使う。(詳しくはこちらを参照してください)。

login() アクション

login アクションを追加できます。このアクションはログインするためにページコンポーネントから呼び出されます:

login ({ commit }, { username, password }) {
  return fetch('/api/login', {
    // クライアントのクッキーをサーバーに送信
    credentials: 'same-origin',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      username,
      password
    })
  })
  .then((res) => {
    if (res.status === 401) {
      throw new Error('Bad credentials')
    } else {
      return res.json()
    }
  })
  .then((authUser) => {
    commit('SET_USER', authUser)
  })
}

logout() メソッド

logout ({ commit }) {
  return fetch('/api/logout', {
    // クライアントのクッキーをサーバーに送信
    credentials: 'same-origin',
    method: 'POST'
  })
  .then(() => {
    commit('SET_USER', null)
  })
}

ページコンポーネント

ユーザーがアプリケーションで認証されているか否かをチェックするために、ページコンポーネント内で $store.state.authUser を使うことができます。

ユーザーが認証されていないときはリダイレクトする

認証されたユーザーのみがコンテンツを閲覧できる /secret ルートを追加してみましょう:

<template>
  <div>
    <h1>Super secret page</h1>
    <router-link to="/">Back to the home page</router-link>
  </div>
</template>

<script>
export default {
  // データをこのコンポーネントにセットする必要がないため fetch() を使う
  fetch ({ store, redirect }) {
    if (!store.state.authUser) {
      return redirect('/')
    }
  }
}
</script>

ユーザーが認証されていなかったときは fetch メソッド内で redirect('/') が呼び出されます。