🤩
Nuxt.js(SSR) + Firebase Authenticationまとめ
概要
初投稿は、Nuxt.js(SSR) + Firebase Authenticationで認証管理を実装する自分なりのベストプラクティスをまとめたいので書きました。この内容には、構文間違いや書き方がよくないなどあると思いますのでその時はコメントなどで教えていただけると自分の為にもなりますのでどうぞよろしくお願いします。
※画質は指摘しないでねw 動かしたかったらこのrepoで
Firebaseの準備
- Firebaseプロジェクトを作成する(Analyticsは本番以外では設定しなくていい)
- Authenticationを開始して、Google認証を有効にする
- RealTime Databaseをユーザー登録情報用に起動(アメリカ鯖で)
- マイアプリにアプリを登録して、Firebase SDK snippetを構成に選択してConfigデータを取得
※あくまで必要最低限と好みで選んでいます。(Cloud FireStoreでも構いません)
Nuxt.jsの準備
-
Nuxtプロジェクトを作成する
yarn create nuxt-app <project-name>
※いくつかの質問(名前、Nuxt オプション、UI フレームワーク、TypeScript、linter、テストフレームワークなど)されます。すべてのオプションをみるには Create Nuxt app を参照してください。
-
モジュールをインストールする
今回利用するモジュールを一括インストールします。yarn add firebase firebase-admin @nuxtjs/pwa @nuxtjs/firebase @nuxtjs/dotenv
- firebase[1]
- firebase-admin[2]
- @nuxtjs/pwa[3]
- @nuxtjs/firebase[4]
- @nuxtjs/dotenv[5]
-
nuxt.config.js
の設定nuxt.config.jsrequire('dotenv').config() modules: [ '@nuxtjs/dotenv', '@nuxtjs/pwa', [ '@nuxtjs/firebase', { config: { apiKey: process.env.apiKey, authDomain: process.env.authDomain, databaseURL: process.env.databaseURL, projectId: process.env.projectId, storageBucket: process.env.storageBucket, messagingSenderId: process.env.messagingSenderId, appId: process.env.appId, measurementId: process.env.measurementId }, services: { auth: { persistence: 'local', initialize: { onAuthStateChangedAction: 'onAuthStateChanged', }, ssr: true } } } ] ], pwa: { meta: false, icon: false, workbox: { importScripts: [ '/firebase-auth-sw.js' ], dev: true } },
-
環境変数の設定
.envapiKey="" authDomain="" databaseURL="" projectId="" storageBucket="" messagingSenderId="" appId="" measurementId=""
Vuex作成
store/index.js
export const state = () => ({
authUser: null
})
export const getters = {
isLoggedIn: state => !!state.authUser
}
export const actions = {
async nuxtServerInit({ dispatch }, ctx) {
if (this.$fire.auth === null) {
throw 'nuxtServerInit Example not working - this.$fire.auth cannot be accessed.'
}
if (ctx.$fire.auth === null) {
throw 'nuxtServerInit Example not working - ctx.$fire.auth cannot be accessed.'
}
if (ctx.app.$fire.auth === null) {
throw 'nuxtServerInit Example not working - ctx.$fire.auth cannot be accessed.'
}
if (ctx.res && ctx.res.locals && ctx.res.locals.user) {
const { allClaims: claims, ...authUser } = ctx.res.locals.user
console.info(
'Auth User verified on server-side. User: ',
authUser,
'Claims:',
claims
)
await dispatch('onAuthStateChanged', {
authUser,
claims,
})
}
},
async onAuthStateChanged({ commit }, { authUser, claims }) {
if (!authUser) {
// ログアウトしたらページ遷移します
await this.$router.push("/login")
commit('RESET_STORE')
return
}
if (authUser && claims) {
try {
// ログインしたらページ遷移します
await this.$router.push("/profile")
} catch (e) {
console.error(e)
}
}
commit('SET_USER', { authUser, claims })
}
}
export const mutations = {
// stateを空にします
RESET_STORE(state) {
state.authUser = null
},
// stateにpayloadをセットします
SET_USER(state, { authUser, claims }) {
state.authUser = {
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerified,
displayName: authUser.displayName,
photoURL: claims.picture,
isAdmin: claims.admin
}
}
}
一般ユーザー向けの実装
Pages作成
ログインページ
pages/login.vue
<template>
<div>
<h1>Login</h1>
<button @click="signInWithGoogle">Sign in with Google</button>
</div>
</template>
<script>
export default {
layout: "default",
name: "login",
methods: {
async signInWithGoogle() {
const provider = new this.$fireModule.default.auth.GoogleAuthProvider();
await this.$fire.auth.signInWithPopup(provider).then(res => {
res.user.getIdToken(true).then(idToken => {
localStorage.setItem('access_token', idToken.toString())
localStorage.setItem('refresh_token', res.user.refreshToken.toString())
})
})
console.log('成功しました')
},
}
}
プロフィールページ
pages/profile.vue
<template>
<div>
<h1>User Profile</h1>
<p v-if="User != null">Your e-mail is {{ User.email }}</p>
<button @click="logout">Logout</button>
</div>
</template>
<script>
export default {
layout: "default",
name: "profile",
middleware: "auth",
data() {
return {
User: this.$store.state.authUser
}
},
methods: {
async logout() {
await this.$fire.auth.signOut()
},
}
}
</script>
Middleware作成
認証ユーザー限定ページ用ミドルウェア
middleware/auth.js
export default function({ store, redirect }) {
if(!store.getters['isLoggedIn']) {
return redirect('/login')
}
}
管理者向け実装
Pages作成
管理者ページ
pages/admin/index.vue
<template>
<div>
<h1>Admin</h1>
</div>
</template>
<script>
export default {
name: "index",
middleware: ["auth", "check-admin"],
}
</script>
プロフィールページ
pages/profile
<template>
<div>
<h1>User Profile</h1>
// 追加
<p v-if="User != null">Your e-mail is {{ User.email }} <span v-if="User.isAdmin">& claims: <button @click="returnAdmin">admin</button></span></p>
<button @click="logout">Logout</button>
</div>
</template>
<script>
export default {
layout: "default",
name: "profile",
middleware: "auth",
data() {
return {
User: this.$store.state.authUser
}
},
methods: {
async logout() {
await this.$fire.auth.signOut()
},
// 追加
async returnAdmin() {
await this.$router.push("/admin")
}
}
}
</script>
Middleware作成
管理者限定ページ用ミドルウェア
middleware/check-admin.js
export default function({ store, redirect }) {
if(!store.getters['isAdminIn']) {
return redirect("/profile")
}
}
Vuex更新
store/index.js
export const getters = {
// 追加
isAdminIn: state => !!state.authUser.isAdmin
}
さらに細かい処理を実装
参考
【Nuxt.js(SSR) × Firebase Authentication】@nuxtjs/firebaseモジュールを使ったログインセッション管理
@nuxtjs/firebase social auth
Discussion
参考記事の「@nuxtjs/firebaseモジュールを使ったログインセッション管理」を書いた者です。
記事のURLを以下に変更しておりますので、お手隙で修正していただけますと幸いです。