このサイトのテーマ『SWELL』を見てみる >>

【12000字で徹底解説】Nuxt.jsで認証機能を実装しよう【SSR入門】

こんにちは。takabeeです。

 

Nuxt.jsを始めたばかりの人にとって、よくありがちな悩みがこちら。

Takabee
Nuxt.jsの認証機能を作るのが難しい!ていうかSSRがそもそもよくわからない!
その悩みをこんな感じで解決していきましょう。
【STEP1】SSRの全体を把握しよう!
【STEP2】実際に認証機能を作って理解しよう!
では早速STEP1から!
ネコ大先生
この記事は【約10分】で読めるニャ。
目次

【STEP1】SSRの全体を把握しよう!

まずはSSRの仕組みを理解していきましょう!

 

とはいえそんな難しいものではなく、カンタンに理解できますので、こちらの記事を5分ほどご覧ください。

SPA・SSR・SSGの違いもしっかり理解できます。

 

あわせて読みたい
SPA・SSR・SSGを5分で理解できるように解説してみた。【これで解決】 こんにちは。takabeeです。   今回はNuxt.jsのSPA, SSR, SSGの違いについて、世界一わかりやすく解説していく記事です。 Nuxt.jsを勉強し始めたばかりの方は、 初...

 

【STEP2】実際に認証機能を作って理解しよう!

【STEP1】でSSRについてしっかり理解できた方は、実際に手を動かしながら理解を深めていきましょう!

今回作る目標物はこんな感じ。

 

・Firebase Authenticationで認証機能を実装
・ログインしたユーザーはトップページに遷移し、ログインしていないユーザーはログインページにリダイレクトさせる

①下準備(プロジェクトの作成、モジュールのインストール)

まずはプロジェクトの作成から。

npm init nuxt-app <好きなプロジェクト名>

僕はプロジェクト名をnuxt-authとしました。

設定はデフォルトで大丈夫です。

 

無事にインストールができたら、@nuxtjs/axiosモジュールをインストールしましょう。

npm install --save @nuxtjs/axios

 

axiosは、サーバーにHTTP通信を使ってアクセスできるモジュールです。

つまりこれを使えば、自分のアプリから簡単にFirebaseにアクセスできます。

 

nuxt.config.jsのmodulesで有効化させましょう。

modules: [
  "@nuxtjs/axios"
]

 

最後にFirebaseプロジェクトの作成を行います。

下の手順に従ってサクサクいきましょう!

 

これでFirebaseのプロジェクトは完了です!

 

Firebase Authenticationの設定を行っていきます。

こちらのサクサクいきましょう!

 

これで下準備は完了!

 

②新規登録・ログインページの作成

新規登録・ログインページの作成をやっていきます。

 

pagesフォルダの中にauthフォルダを新規作成し、index.vueを追加します。

pages/auth/index.vueを作ろう!

 

その中にこんな感じで新規登録・ログインページを書きます。

<template>
 <div class="auth-page container">
  <div class="auth-container row">
   <form>
    <input type="email" placeholder="email address">
     <input type="password" placeholder="password">
      <button type="submit" class="is-style-btn_normal btn-primary">{{ isLogin ? "Login" : "Singup" }}</button>
      <a class="switch-link" type="submit" @click="isLogin = !isLogin">Switch to {{ isLogin? "Signup" : "Login" }}</a>
   </form>
  </div>
 </div>
</template>

<script>
 export default {
  data() {
   return {
    isLogin: true
   }
  }
 };
</script>

<style scoped>
 .switch-link {
  cursor: pointer;
  color: blue;
 }
</style>

これで新規登録・ログインページが出来上がりました。表示はこんな感じ。

 

 

メチャクチャダサいけどまあ、、、テストだからね??

よしとしましょう。

 

③新規登録機能を加える

新規登録機能を加えていきましょう。

 

APIキーを得るために、Firebaseの設定画面に飛び、APIキーを手に入れましょう。

 

 

コピーしたAPIキーをnuxt.config.jsのenvセクションで、ApiKey環境変数に格納します。

env: {
  //文字列になるように「"」で囲んでね!
  ApiKey: "ここにコピーしたAPI KEYを貼り付ける" 
}

 

つづいてFirebase Rest APIのページにアクセスします。

Firebase Rest APIのページに飛ぶ >>

 

[Sign up with email / password]の欄に飛び、[Endpoint]をコピーします。

 

auth/index.vueに戻り、こんな感じでコーディングします。

解説していくので、いまは全部理解できなくても大丈夫。

 

<template>
 <div class="auth-page container">
  <div class="auth-container row">
   <form @submit.prevent="onSubmit">
    <input type="email" placeholder="email address">
    <input type="password" placeholder="password">
    <button type="submit" class="is-style-btn_normal btn-primary">{{ isLogin ? "Login" : "Singup" }}</button>
    <a class="switch-link" type="submit" @click="isLogin = !isLogin">Switch to {{ isLogin? "Signup" : "Login" }}</a>
   </form>
  </div>
 </div>
</template>

<script>
 export default {
  data() {
   return {
    isLogin: true,
    email: "",
    password: ""
   }
  },
  methods: {
   onSubmit() {
    const signupData = {
     email: this.email,
     password: this.password,
     returnSecureToken: true
    }
    this.$axios.$post("https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=" + process.env.ApiKey, signupData)
   }
  }
 };
</script>

<style scoped>
 .switch-link {
  cursor: pointer;
  color: blue;
 }
</style>

さっそく上のコードの解説をしていきます。

まずボタンを押したときフォームが送信され、<form>タグには

@submit.prevent="onSubmit"

とある通り、onSubmitメソッドが発火します。

onSubmitメソッドではaxiosでのPOST通信が行われています。

axiosのPOST通信の引数には

this.$axios.$post(リクエストを送るURL, 送るデータ)

を指定します。

なのでURLはFirebaseの新規登録先のURL、送信データにはユーザーが入力したemail、passwordを入れます。

this.$axios.$post("https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=" + process.env.ApiKey, signupData)

ただ一つだけ、見慣れない

returnSecureToken: true

というパラメータがあります。

これは公式ドキュメントに記載されていますが、トークンを返すか返さないかをBoolean(trueかfalse)で指定できます。

トークンを返すか返さないかを指定できるよ!

トークンというのはユーザーがログインしているかいないかを判断するための唯一の値、という認識でいまは大丈夫です。

それを今回は僕たちが受け取りたいので、trueとしています。

これで新規登録機能の作成は完了です!

Takabee
トークンはまたあとで出てくるけど、SSRにおいてこのトークンはめちゃくちゃ大事です。

④ログイン機能の実装

これからログイン機能の実装をしていきます。

ログインするときは、axios通信でPOSTするURLが、新規登録するときと異なります。

 

なのでもう一度Firebase Rest APIのページに行き、ログイン用のURLをゲットしましょう。まずこちらにアクセスしてください。

Firebase Rest APIのページに飛ぶ >>

 

[Sign in with email / password]の欄にいき、[Endpoint]をコピーしてください。

さっきの要領でログイン用のEndpointをコピーしよう!

 

そしたらauth/index.vueに戻り、ログイン時には今コピーしたURLでPOSTします。

新規登録時にはさっきのURLでPOSTするようにコーディングします。

 

<template>
 <div class="auth-page container">
  <div class="auth-container row">
   <form @submit.prevent="onSubmit">
    <input type="email" placeholder="email address">
    <input type="password" placeholder="password">
    <button type="submit" class="is-style-btn_normal btn-primary">{{ isLogin ? "Login" : "Singup" }}</button>
    <a class="switch-link" type="submit" @click="isLogin = !isLogin">Switch to {{ isLogin? "Signup" : "Login" }}</a>
   </form>
  </div>
 </div>
</template>

<script>
 export default {
  data() {
   return {
    isLogin: true,
    email: "",
    password: ""
   }
  },
  methods: {
   onSubmit() {
    //デフォルトではauthUrlはログイン用のURL
    const authUrl = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=" + process.env.ApiKey;
  if(!this.isLogin) { 
     //isLoginがfalse、つまり新規登録時のみさっきのURLに変える。
     authUrl = "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=" + process.env.ApiKey
    }
    const signupData = {
     email: this.email,
     password: this.password,
     returnSecureToken: true
    }
    this.$axios.$post(authUrl, signupData)
   }
  }
 };
</script>

<style scoped>
 .switch-link {
  cursor: pointer;
  color: blue;
 }
</style>

 

これでisLoginがtrueもしくはfalseのどちらかによって、POST送信するURLを変えることができるようになりました。

 

Takabee
ここから先は実際に認証機能に入っていきます。ここまで難しかったので、いったん休憩して、回復したらまた始めましょう。

⑤Vuexへのトークンの保存

ここでいったん今まで書いてきたonSubmitメソッドをVuexに移します。

「Vuexってなに?」という方は、こちらの記事を5分だけ眺めてから戻ってきてください。

 

あわせて読みたい
Nuxt.jsとは?超シンプルなTODOアプリを作りながらサクッと理解しよう!【メチャクチャ簡単です】 こんにちは。takabeeです。   今回はNuxt.jsをTODOアプリを作りながらサクッと理解してしまおう、という記事です。 この記事を見てくれている方は、 Nuxt初心者 Nu...

 

storeフォルダの下にindex.jsを作成して、こんな感じにします。

 

import Vuex from 'vuex';
import axios from 'axios';

const createStore = () => {
 return new Vuex.Store({
  state: {
   token: "",
	 user: {}
  },

  getters: {
  },

  mutations: {
   //stateのtokenにトークンを保存するよ!
   setToken(state, token) {
    state.token = token;
   },
	 //stateのtokenにトークンを保存するよ!
   setUser(state, user) {
    state.user = user;
   },
  },

  actions: {
   authenticateUser({commit}, authData) {
    const authUrl = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=" + process.env.ApiKey;
    if(!authData.isLogin) {
     //isLoginがfalse、つまり新規登録時のみさっきのURLに変える。
     authUrl = "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=" + process.env.ApiKey
    }

    //axios通信の結果をreturnすることで、もう一度index.vueのonSubmitメソッドに処理が返されるよ!
    return this.$axios.$post("https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=" + process.env.ApiKey, {
     email: authData.email,
     password: authData.password,
     returnSecureToken: true
    })
    .then(result => {
    //returnSecureTokenをtrueにしたとき、resultのidTokenにトークンが入っているよ!
     commit('setToken', result.idToken)
     $nuxt.$router.push('/')
    })
    .catch(e => console.log(e))
    }
   }
  })
}

export default createStore;
これでVuexへのトークンの保存が完了です。

⑥トークンを使って認証機能を加える

ここで始めてトークンを使って、認証機能を加えていきます。

 

まず覚えておいてほしいことは、SSRではサーバー側でVuexの値を読み取ることはできないということです。

つまりいくらログインしてVuexにトークンを保管していても、ページを更新してリクエストが飛んだらVuexのトークンの値は初期化され、再度ログインし直さなければなりません。

 

これでは


ユーザー
さっきログインしたばかりなのに、またログインすんの!?

とユーザーにはかなり不便です。

 

ではどうするかというと、Cookieというものを使います。

Cookieはブラウザにデータを保存し、クライアントでもサーバーでもアクセスできるデータ、といまは認識しておいてください。

 

Vuexはクライアントのみアクセス可能!

全体の処理としては以下のようになります。

①ユーザーが新規登録orログインしたら、Cookieにトークンを保存
②SSR時(ページリロード時)に、サーバーでCookieからトークンを取得し、ユーザー情報を復元してVuexに保存
③middlewareでVuexのトークンからログイン判断し、ログインしてなかったらログインページにリダイレクト

①ユーザーがログインしたら、Cookieにトークンを保存

まずここからやっていきます。

①ユーザーがログインしたら、Cookieにトークンを保存
②SSR時(ページリロード時)に、サーバーでCookieからトークンを取得し、ユーザー情報を復元してVuexに保存
③middlewareでVuexのトークンからログイン判断し、ログインしてなかったらログインページにリダイレクト
まずCookieを使うのに、必要なjs-cookieというモジュールをインストールします。
npm install --save js-cookie
それを使えるように、store/index.jsにインポートします。
import Cookie from 'js-cookie'
Cookieに値を保存するときは、
Cookie.set(保存する名前, 保存する値)
と指定します。
なので、これをユーザーがログインしたときに発動するstoreのauthenticateUserメソッドに書いていきましょう。
authenticateUser({commit}, authData) {
 const authUrl = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=" + process.env.ApiKey;
  if(!authData.isLogin) {
   //isLoginがfalse、つまり新規登録時のみさっきのURLに変える。
   authUrl = "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=" + process.env.ApiKey
  }

  //axios通信の結果をreturnすることで、もう一度index.vueのonSubmitメソッドに処理が返されるよ!
  return this.$axios.$post("https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=" + process.env.ApiKey, {
   email: authData.email,
   password: authData.password,
   returnSecureToken: true
  })
  .then(result => {
  //returnSecureTokenをtrueにしたとき、resultのidTokenにトークンが入っているよ!
   commit('setToken', result.idToken)
   Cookie.set('jwt', result.idToken) //追加
  })
  .catch(e => console.log(e))
  }
 }
こんな感じですね。
これでユーザーが新規登録・ログインしたときに、jwtという名前でCookieにトークンが保存されます。

②SSR時(ページリロード時)に、サーバーでCookieからトークンを取得し、ユーザー情報を復元してVuexに保存

つづいて、こちらをやっていきましょう。

①ユーザーがログインしたら、Cookieにトークンを保存
②SSR時(ページリロード時)に、サーバーでCookieからトークンを取得し、ユーザー情報を復元してVuexに保存
③middlewareでVuexのトークンからログイン判断し、ログインしてなかったらログインページにリダイレクト
Vuexに初期値を与えるのはstore/index.jsのnuxtServerInitで行います。完成コードはこちら。
nuxtServerInit({ commit, getters }, { req }) {
  if (process.server && process.static) return;
  if (!req.headers.cookie) return;

  //"jwt"から始まるCookieを取得。
  const accessTokenCookie = req.headers.cookie
    .split(";")
    .find(c => c.trim().startsWith("jwt="));

  //"jwt"から始まるCookieの「値」を取得。idTokenのこと。
  const token = accessTokenCookie.split("=")[1];

  if (token) {
	  //トークンがあった時
    admin
      .auth()
      .verifyIdToken(token) 
			//decodedTOken(トークンから復元した情報)にユーザー情報が格納されている。
      .then(decodedToken => {
        const userInfo = {
          email: decodedToken.email,
          uid: decodedToken.uid
        };

        commit("setToken", token) //トークンを保存
        commit("setUser", userInfo); //ユーザー情報を保存
        $nuxt.$router.push("/");
      })
      .catch(e => {
        console.log(e);
        commit("resetUser");
      });
  } else {
    commit("resetUser");
  }
},
上のコードの解説をしていきます。
トークンに限らず、Cookieの値はすべて
req.headers.cookie
というところに保存されているので、ここからCookieに保存したトークンデータを取ります。
なかったらそのまま処理を返し、終了してます。
if(!req.headers.cookie) return;
あれば、Cookieからトークンを取り出します。
const jwtCookie = req.headers.cookie.split(";").find(c => c.trim().startsWith("jwt="))
として、cookieのそれぞれの値を”;”を目印に区切っています。
そのあと”jwt=”から始まる箇所をCookieから取り出して、jwtCookie変数に代入します。
jwtCookie変数の中身はこんな感じになっています。
jwt="保存したトークン"
なので
const token = jwtCookie.split("=")[1];
として、jwtと保存したトークンを区切ってtoken変数に格納し、最後に
commit('setToken', token)
でVuexにトークンを保存しています。
トークンがあったとき、
//トークンがあった時
    admin
      .auth()
      .verifyIdToken(token) 
			//decodedTOken(トークンから復元した情報)にユーザー情報が格納されている。
      .then(decodedToken => {
        const userInfo = {
          email: decodedToken.email,
          uid: decodedToken.uid
        };

        commit("setUser", userInfo);
        $nuxt.$router.push("/");
      })
      .catch(e => {
        console.log(e);
        commit("resetUser");
      });
として、decodedTokenから復元したユーザー情報を取得し、Vuexに格納しています。
トークンもVuexに保存しています。
これで、トークンからのユーザー情報の復元が完了しました。

③middlewareでVuexのトークンからログイン判断し、ログインしてなかったらログインページにリダイレクト

最後にmiddlewareでルーティング制御をしていきます。

 

①ユーザーがログインしたら、Cookieにトークンを保存
②SSR時(ページリロード時)に、サーバーでCookieからトークンを取得し、ユーザー情報を復元してVuexに保存
③middlewareでVuexのトークンからログイン判断し、ログインしてなかったらログインページにリダイレクト

まずstore/index.jsのgettersに、isAuthenticatedというトークンを返すゲッターを設定します。

isAuthenticated(state) {
 return state.token != null;
}

 

つぎにmiddlewareフォルダに、auth.jsというファイルを作ります。

 

その中にVuexにトークンがなかったら、ログインページに飛ばす処理を書いていきます。

export default function(context) {
 if(!context.store.getters.isAuthenticated) {
  //storeのgettersにtokenがなかったら、ログインページに飛ばしているよ!
  context.redirect('/auth')
 }
}

 

middlewareを実際に使うときには、必ず登録をしなければなりません。

今回はnuxt.config.jsに登録します。

middleware: ['auth']

 

これですべての認証は完了です!

Takabee
めちゃくちゃ疲れた。。。

まとめ:難しいけど、慣れるから大丈夫!

ここまで、お疲れ様でした。(自分にも言いたい。。。)

 

まとめると、

SSRではVuexにアクセスできないため、クライアントからサーバーにデータを渡すときにはCookieというものを使いました。
新規登録時・ログイン時にはCookieにトークンを保存し、ページ更新時にはCookieを使ってユーザー情報を復元し、Vuexに保存しました。

 

言葉にすると長いですが、自分でコーディングしてみるとどんどん頭に入ってきます。

ぜひトライしてみてください。

 

では、また次の記事でお会いしましょう!

ネコ大先生
最後まで読んでくれて、ありがとうニャ!
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Webエンジニア兼ブロガー。Nuxt.js, Wordpress, SEO対策などの案件を承っております。両感音性難聴3級持ち。
お仕事の依頼はこちらから。
https://www.lancers.jp/profile/takabeee

コメント

コメントする

目次
閉じる