パスワード更新画面を追加

に公開

関連記事

関連記事一覧

GitHub

https://github.com/nakajima-sh-cnctor/nuxt4-firebase-chat

パスワード更新画面

利用目的はログイン認証が完了しているユーザがパスワードを変更するために利用されます。
「パスワードを忘れた」人用のものではないので注意が必要です。

入力項目

  • 現在のパスワード
  • 新しいパスワード
  • 新しいパスワード(確認用)

新しいパスワード(確認用)はバリデーションとして”新しいパスワード”と一致しているかをルールに設定する

ディレクトリ構成

app/
├── components/
│   └── PasswordChangeForm.vue    # パスワード変更フォームコンポーネント
└── pages/
    └── home/
        └── change-password.vue   # パスワード変更ページ

レイアウト

入力項目のコンポーネントの追加

app/components/PasswordChangeForm.vue
<script setup lang="ts">
const currentPassword = ref('')
const newPassword = ref('')
const confirmPassword = ref('')
const showCurrent = ref(false)
const showNew = ref(false)
const showConfirm = ref(false)
const form = ref()
const loading = ref(false)

const passwordRules = [(v: string) => !!v || 'パスワードは必須です']

const newPasswordRules = [
  (v: string) => !!v || '新しいパスワードは必須です',
  (v: string) => v.length >= 6 || 'パスワードは6文字以上で入力してください',
]

const confirmPasswordRules = [
  (v: string) => !!v || 'パスワードの確認は必須です',
  (v: string) => v === newPassword.value || 'パスワードが一致しません',
]

const emit = defineEmits(['submit'])

const submit = async () => {
  if (form.value) {
    const { valid } = await form.value.validate()
    if (!valid) return

    loading.value = true
    emit('submit', {
      currentPassword: currentPassword.value,
      newPassword: newPassword.value,
    })
    loading.value = false
  }
}
</script>

<template>
  <v-form ref="form" @submit.prevent="submit">
    <v-text-field
      v-model="currentPassword"
      :append-icon="showCurrent ? 'mdi-eye' : 'mdi-eye-off'"
      :rules="passwordRules"
      :type="showCurrent ? 'text' : 'password'"
      label="現在のパスワード"
      variant="underlined"
      autocomplete="current-password"
      @click:append="showCurrent = !showCurrent"
    />
    <v-text-field
      v-model="newPassword"
      :append-icon="showNew ? 'mdi-eye' : 'mdi-eye-off'"
      :rules="newPasswordRules"
      :type="showNew ? 'text' : 'password'"
      label="新しいパスワード"
      variant="underlined"
      autocomplete="new-password"
      @click:append="showNew = !showNew"
    />
    <v-text-field
      v-model="confirmPassword"
      :append-icon="showConfirm ? 'mdi-eye' : 'mdi-eye-off'"
      :rules="confirmPasswordRules"
      :type="showConfirm ? 'text' : 'password'"
      label="新しいパスワード(確認)"
      variant="underlined"
      autocomplete="new-password"
      @click:append="showConfirm = !showConfirm"
    />
    <v-btn :loading="loading" color="primary" class="mt-4" type="submit" block>
      パスワードを変更
    </v-btn>
  </v-form>
</template>

パスワード更新ページ

app/pages/home/change-password.vue
<script setup lang="ts">
import PasswordChangeForm from '~/components/PasswordChangeForm.vue'

const { changePassword } = useAuth()

const error = ref('')
const success = ref(false)

const handlePasswordChange = async (data: {
  currentPassword: string
  newPassword: string
}) => {
  error.value = ''
  success.value = false

  try {
    const result = await changePassword(data.newPassword)

    if (result.error) {
      error.value = result.error.message
    } else {
      success.value = true
      // 3秒後にホームページにリダイレクト
      setTimeout(() => {
        navigateTo('/home')
      }, 3000)
    }
  } catch (err) {
    console.error(err)
    error.value = 'パスワード変更中にエラーが発生しました'
  }
}
</script>

<template>
  <v-card class="mx-auto my-8" max-width="500">
    <v-card-item>
      <v-card-title>パスワード変更</v-card-title>
      <v-card-subtitle>
        現在のパスワードを入力して、新しいパスワードに変更してください
      </v-card-subtitle>
    </v-card-item>
    <v-card-item>
      <v-alert
        v-if="error"
        type="error"
        :text="error"
        variant="tonal"
        class="mb-4"
      />
      <v-alert
        v-if="success"
        type="success"
        text="パスワードが正常に変更されました"
        variant="tonal"
        class="mb-4"
      />
      <PasswordChangeForm @submit="handlePasswordChange" />
      <div class="mt-4 text-center">
        <v-btn variant="text" color="primary" @click="navigateTo('/home')">
          ホームに戻る
        </v-btn>
      </div>
    </v-card-item>
  </v-card>
</template>

Firebaseのパスワード変更メソッド

app/composables/useAuth.ts
  // パスワード変更
  const changePassword = async (newPassword: string) => {
    if (!auth || !user.value) {
      return {
        error: new Error('ユーザーが認証されていません'),
      }
    }
    try {
      await updatePassword(user.value, newPassword)
      return { error: null }
    } catch (error) {
      return { error: error as Error }
    }
  }

FirebaseupdatePasswordは、ユーザーがログインしている状態で自身のパスワードを変更するための機能です。セキュリティを確保するため、この操作を実行する前にユーザーの再認証を要求することが強く推奨されています。


主な特徴と注意点

updatePasswordは、Firebase Authenticationサービスの一部として提供されており、現在認証されているユーザーオブジェクトに対して呼び出します。

  • 現在のユーザー: このメソッドは、現在サインインしているユーザーのパスワードのみを変更できます。他のユーザーのパスワードを管理者として変更するための直接的なメソッドではありません。
  • 再認証の重要性: パスワードの変更は非常に重要な操作です。そのため、ユーザーが最近ログインしたことを確認するために、updatePasswordを呼び出す前に再認証を求めることがセキュリティのベストプラクティスとされています。これにより、アカウントが乗っ取られた場合に、第三者が勝手にパスワードを変更するのを防ぎます。
  • エラー処理: ネットワークエラーや、パスワードが弱すぎる場合(Firebaseプロジェクトでパスワードの複雑性を設定している場合)など、さまざまな理由でパスワードの更新が失敗することがあります。そのため、適切なエラーハンドリングを実装することが重要です。

ウェブアプリケーションでは、firebase/authモジュールからgetAuthupdatePassword関数を利用します。

セキュリティに関する考慮事項 (auth/requires-recent-login)

updatePasswordを呼び出した際に最もよく遭遇するエラーの一つが auth/requires-recent-login です。これは、ユーザーが最後にログインしてから時間が経過していることを示します。

このエラーが発生した場合、ユーザーに再度ログイン情報(メールアドレスと現在のパスワード、または他の認証プロバイダ)を入力させ、再認証プロセスを実行する必要があります。再認証が成功した後に updatePassword を呼び出すことで、操作を安全に完了できます。

この仕組みにより、例えばユーザーが公共のコンピュータでログインしたまま席を離れてしまった場合でも、第三者がパスワードを勝手に変更してしまうリスクを大幅に軽減できます。

GitHubで編集を提案

Discussion