こんにちは。takabeeです。
Nuxt.jsを始めたばかりの人にとって、よくありがちな悩みがこちら。
【STEP2】実際に認証機能を作って理解しよう!
【STEP1】SSRの全体を把握しよう!
まずはSSRの仕組みを理解していきましょう!
とはいえそんな難しいものではなく、カンタンに理解できますので、こちらの記事を5分ほどご覧ください。
SPA・SSR・SSGの違いもしっかり理解できます。

【STEP2】実際に認証機能を作って理解しよう!
【STEP1】でSSRについてしっかり理解できた方は、実際に手を動かしながら理解を深めていきましょう!
今回作る目標物はこんな感じ。
・ログインしたユーザーはトップページに遷移し、ログインしていないユーザーはログインページにリダイレクトさせる
①下準備(プロジェクトの作成、モジュールのインストール)
まずはプロジェクトの作成から。
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のページにアクセスします。
[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としています。
これで新規登録機能の作成は完了です!
④ログイン機能の実装
これからログイン機能の実装をしていきます。
ログインするときは、axios通信でPOSTするURLが、新規登録するときと異なります。
なのでもう一度Firebase Rest APIのページに行き、ログイン用のURLをゲットしましょう。まずこちらにアクセスしてください。
[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を変えることができるようになりました。
⑤Vuexへのトークンの保存
ここでいったん今まで書いてきたonSubmitメソッドをVuexに移します。
「Vuexってなに?」という方は、こちらの記事を5分だけ眺めてから戻ってきてください。

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;
⑥トークンを使って認証機能を加える
ここで始めてトークンを使って、認証機能を加えていきます。
まず覚えておいてほしいことは、SSRではサーバー側でVuexの値を読み取ることはできないということです。
つまりいくらログインしてVuexにトークンを保管していても、ページを更新してリクエストが飛んだらVuexのトークンの値は初期化され、再度ログインし直さなければなりません。
これでは

ユーザー
とユーザーにはかなり不便です。
ではどうするかというと、Cookieというものを使います。
Cookieはブラウザにデータを保存し、クライアントでもサーバーでもアクセスできるデータ、といまは認識しておいてください。
Vuexはクライアントのみアクセス可能!
全体の処理としては以下のようになります。
②SSR時(ページリロード時)に、サーバーでCookieからトークンを取得し、ユーザー情報を復元してVuexに保存
③middlewareでVuexのトークンからログイン判断し、ログインしてなかったらログインページにリダイレクト
①ユーザーがログインしたら、Cookieにトークンを保存
まずここからやっていきます。
②SSR時(ページリロード時)に、サーバーでCookieからトークンを取得し、ユーザー情報を復元してVuexに保存
③middlewareでVuexのトークンからログイン判断し、ログインしてなかったらログインページにリダイレクト
npm install --save js-cookie
import Cookie from 'js-cookie'
Cookie.set(保存する名前, 保存する値)
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))
}
}
②SSR時(ページリロード時)に、サーバーでCookieからトークンを取得し、ユーザー情報を復元してVuexに保存
つづいて、こちらをやっていきましょう。
②SSR時(ページリロード時)に、サーバーでCookieからトークンを取得し、ユーザー情報を復元してVuexに保存
③middlewareでVuexのトークンからログイン判断し、ログインしてなかったらログインページにリダイレクト
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");
}
},
req.headers.cookie
if(!req.headers.cookie) return;
const jwtCookie = req.headers.cookie.split(";").find(c => c.trim().startsWith("jwt="))
jwt="保存したトークン"
const token = jwtCookie.split("=")[1];
commit('setToken', token)
//トークンがあった時
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");
});
③middlewareでVuexのトークンからログイン判断し、ログインしてなかったらログインページにリダイレクト
最後にmiddlewareでルーティング制御をしていきます。
②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']
これですべての認証は完了です!
まとめ:難しいけど、慣れるから大丈夫!
ここまで、お疲れ様でした。(自分にも言いたい。。。)
まとめると、
新規登録時・ログイン時にはCookieにトークンを保存し、ページ更新時にはCookieを使ってユーザー情報を復元し、Vuexに保存しました。
言葉にすると長いですが、自分でコーディングしてみるとどんどん頭に入ってきます。
ぜひトライしてみてください。
では、また次の記事でお会いしましょう!
コメント