【Vue.js】Firebase Authenticationでメールアドレス認証を実装する方法

【Vue.js】Firebase Authenticationでメールアドレス認証を実装する方法 Vue.js

Firebase Authenticationを使えば、メールアドレスとパスワードによる認証を短時間で実装できます。この記事では、Vue 3(Composition API)+ Firebase v9(modular SDK)での導入手順から、サインアップ/ログイン/ログアウト、状態監視、ルートガード、パスワードリセット、メール検証(Email Verification)までをまとめて解説します。

準備:FirebaseプロジェクトとAuthの有効化

  1. Firebase Consoleでプロジェクト作成 → 「ウェブアプリを追加」から構成情報(apiKey 等)を取得
  2. Authentication > サインイン方法 で「メール/パスワード」を有効化(必要に応じて「メールリンク」も)

依存関係のインストール

npm i firebase

Firebase初期化(authインスタンスのエクスポート)

// src/lib/firebase.ts
import { initializeApp } from 'firebase/app'
import { getAuth, setPersistence, browserLocalPersistence } from 'firebase/auth'

const firebaseConfig = {
  apiKey: 'YOUR_API_KEY',
  authDomain: 'YOUR_PROJECT.firebaseapp.com',
  projectId: 'YOUR_PROJECT_ID',
  appId: 'YOUR_APP_ID'
}

const app = initializeApp(firebaseConfig)
export const auth = getAuth(app)

// 永続化(タブを跨いでログイン状態維持)
setPersistence(auth, browserLocalPersistence)

サインアップ/ログイン/ログアウトのComposable

// src/composables/useAuth.ts
import { ref } from 'vue'
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  sendEmailVerification,
  sendPasswordResetEmail,
  signOut,
  onAuthStateChanged,
  User
} from 'firebase/auth'
import { auth } from '@/lib/firebase'

const currentUser = ref<User | null>(null)
const loading = ref(true)
const errorMsg = ref('')

// 認証状態を監視(アプリ共通で1回セット)
onAuthStateChanged(auth, (user) => {
  currentUser.value = user
  loading.value = false
})

function friendlyError(e: unknown) {
  const code = (e as any)?.code || ''
  if (code.includes('auth/email-already-in-use')) return 'このメールは既に登録済みです'
  if (code.includes('auth/invalid-email')) return 'メールアドレスの形式が正しくありません'
  if (code.includes('auth/weak-password')) return 'パスワードは6文字以上にしてください'
  if (code.includes('auth/user-not-found') || code.includes('auth/wrong-password')) return 'メールまたはパスワードが違います'
  return 'エラーが発生しました'
}

export function useAuth() {
  const signUp = async (email: string, password: string) => {
    errorMsg.value = ''
    try {
      const cred = await createUserWithEmailAndPassword(auth, email, password)
      // メール検証リンクを送信(任意)
      await sendEmailVerification(cred.user)
      return cred.user
    } catch (e) {
      errorMsg.value = friendlyError(e)
      throw e
    }
  }

  const signIn = async (email: string, password: string) => {
    errorMsg.value = ''
    try {
      const cred = await signInWithEmailAndPassword(auth, email, password)
      return cred.user
    } catch (e) {
      errorMsg.value = friendlyError(e)
      throw e
    }
  }

  const resetPassword = async (email: string) => {
    errorMsg.value = ''
    try {
      await sendPasswordResetEmail(auth, email)
    } catch (e) {
      errorMsg.value = friendlyError(e)
      throw e
    }
  }

  const logout = async () => {
    await signOut(auth)
  }

  return { currentUser, loading, errorMsg, signUp, signIn, resetPassword, logout }
}

サインアップ/ログインフォームの例

<template>
  <h2>ログイン</h2>
  <form @submit.prevent="onSignIn">
    <input v-model="email" type="email" placeholder="メールアドレス" required />
    <input v-model="password" type="password" placeholder="パスワード" required minlength="6" />
    <button :disabled="submitting">ログイン</button>
  </form>

  <p v-if="errorMsg" style="color:#c00">{{ errorMsg }}</p>

  <hr />

  <h2>新規登録</h2>
  <form @submit.prevent="onSignUp">
    <input v-model="email" type="email" placeholder="メールアドレス" required />
    <input v-model="password" type="password" placeholder="パスワード" required minlength="6" />
    <button :disabled="submitting">登録</button>
  </form>

  <button @click="onReset" :disabled="!email || submitting">パスワードをお忘れですか?</button>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useAuth } from '@/composables/useAuth'

const email = ref('')
const password = ref('')
const submitting = ref(false)
const { signIn, signUp, resetPassword, errorMsg } = useAuth()

async function onSignIn() {
  submitting.value = true
  try { await signIn(email.value, password.value) }
  finally { submitting.value = false }
}

async function onSignUp() {
  submitting.value = true
  try {
    await signUp(email.value, password.value)
    alert('確認メールを送信しました。メール内のリンクで検証してください。')
  } finally { submitting.value = false }
}

async function onReset() {
  submitting.value = true
  try {
    await resetPassword(email.value)
    alert('パスワードリセット用のメールを送信しました。')
  } finally { submitting.value = false }
}
</script>

メール検証(Email Verification)の運用例

ビジネス要件によっては「メール検証済みユーザーのみ利用可」にする必要があります。以下は、未検証ユーザーのアクセスを制限する例です。

// src/router/guards/emailVerified.ts
import type { Router } from 'vue-router'
import { useAuth } from '@/composables/useAuth'

export function registerEmailVerifiedGuard(router: Router) {
  const { currentUser, loading } = useAuth()

  router.beforeEach((to, _from, next) => {
    if (loading.value) return next() // 初期同期中は通す
    const needVerified = to.meta.requiresVerified === true
    const user = currentUser.value

    if (!needVerified) return next()
    if (user && user.emailVerified) return next()
    // 未検証
    next({ name: 'VerifyNotice' })
  })
}

ルートガード(未ログインをログインページへ)

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { useAuth } from '@/composables/useAuth'

const routes = [
  { path: '/login', name: 'Login', component: () => import('@/pages/Login.vue') },
  { path: '/', name: 'Home', component: () => import('@/pages/Home.vue'), meta: { requiresAuth: true } }
]

const router = createRouter({ history: createWebHistory(), routes })

router.beforeEach((to, _from, next) => {
  const { currentUser, loading } = useAuth()
  if (loading.value) return next() // 初回同期中は保留せず遷移(ページ側でローディング表示推奨)

  if (to.meta.requiresAuth && !currentUser.value) {
    return next({ name: 'Login', query: { redirect: to.fullPath } })
  }
  next()
})

export default router

ログアウトボタンの例

<template>
  <button @click="onLogout">ログアウト</button>
</template>

<script setup lang="ts">
import { useAuth } from '@/composables/useAuth'
const { logout } = useAuth()
async function onLogout() { await logout() }
</script>

セキュリティ・実運用のポイント

  • メール検証の強制:重要機能は user.emailVerified を確認してガード
  • バックエンドの保護:Cloud Functions / API では Firebase Admin SDK でIDトークンを検証
  • 永続化戦略:共有PCでは browserSessionPersistence を選択するなど要件に応じて設定
  • エラーメッセージ:ユーザー向けにわかりやすいメッセージへ変換(上記 friendlyError 参照)
  • レート制限:パスワードリセットや検証メールの連打対策をUI側で

まとめ

Vue 3 と Firebase Authentication を組み合わせると、メールアドレス認証を短時間で実装できます。ComposableでAPI呼び出しを共通化し、ルートガードでアクセス制御、パスワードリセットとメール検証を組み合わせれば、実運用に耐える認証基盤が完成します。必要に応じてGoogle/AppleなどのOAuthプロバイダも同一の構成に追加できます。