Firebase② Firebase Authentication

先週は、Firebase Authenticationによる、Googleアカウントでのログインについて取り上げました。今回は、メールアドレスとパスワードによるログインについて取り上げます。

メールアドレスとパスワードによる認証

メールアドレスとパスワードによる認証を有効にする。

まずはじめに、前回同様認証を利用するには、その認証方法をコンソール上で有効にする必要があります。メール/パスワードプロパイダを有効にします。

スクリーンショット 20200419 17.20.55.png

スクリーンショット 20200419 17.24.05.png

メール/パスワードプロパイダを有効にすると、コンソール上でユーザーを追加することができます。

Usersタグをクリックしてユーザーを追加ボタンをクリックします。

スクリーンショット 20200419 17.25.08.png

適当なメールアドレスとパスワードを入力して、ユーザーを追加ボタンをクリックします。 ユーザーの一意となるUIDが付与され、今後このユーザーを利用してログインができるようになります。

ログインフォームを構築する。

予め用意されたUIを利用する

ログインフォームには、Firebaseに予め用意されているUIを利用することができます。 UIを利用するためには追加で以下のCDNを読み込みます。

<script src="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.js"></script>
<link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.css" />

jsファイルに、次のようにUIを初期化するコードを書きます。 ログイン方法には、メールアドレスとパスワードによる認証を指定します。

const ui = new firebaseui.auth.AuthUI(firebase.auth());
ui.start('#firebaseui-auth-container', {
  signInOptions: [
    firebase.auth.EmailAuthProvider.PROVIDER_ID
  ],
  // Other config options...
})

ログインウィジェットがレンダリングされるDOMを用意します。ui.startの第一引数に指定されているセレクターに描画されます。(この例では#firebaseui-auth-container

最終的なHTMLは次のようになります。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <script src="https://www.gstatic.com/firebasejs/7.14.0/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/7.14.0/firebase-auth.js"></script>
  <script src="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.js"></script>
  <link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.css" />  
  <title>Firebase Auth</title>
</head>
<body>
  <_div id="firebaseui-auth-container"></_div>
  <script>
    // Your web app's Firebase configuration
    var firebaseConfig = {
      apiKey: "AIzaSyDPOIelluKaZv8--wAksjl9CZrrOdqYJ40",
      authDomain: "awesome-wares-264812.firebaseapp.com",
      databaseURL: "https://awesome-wares-264812.firebaseio.com",
      projectId: "awesome-wares-264812",
      storageBucket: "awesome-wares-264812.appspot.com",
      messagingSenderId: "850229978924",
      appId: "1:850229978924:web:8c88bd3497c24449766e89"
    };
    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
    // const provider = new firebase.auth.GoogleAuthProvider()
  </script>
  <script src="./firebase.js"></script>
</body>
</html>

次のようなログイン画面が表示されました。 さきほど作成したユーザーのメールアドレスを入力してみましょう。

スクリーンショット 20200419 18.08.34.png

このままパスワードを入力すれば、ログインが完了します。 (ログイン後の処理を書いていないため、とくになにもおきませんが) スクリーンショット 20200419 18.11.08.png

また、存在しないユーザーのメールアドレスを入力した場合、そのまま新しいユーザーを作成することができます。

スクリーンショット 20200419 18.13.15.png

独自のUIコンポーネントを利用する

FirebaseのUIを利用して、簡単にログイン機能を利用することができました。 しかし、独自のUIコンポーネントを利用したいことでしょう。 また、ここからはVue.jsを利用したWebアプリケーションを例にして説明していきます。(すでに作ったものを利用したいので)

Vue CLIアプリ作成

いつもどおりVue CLIによりVueアプリを作成します。

vue create firebase-app

どうせ使うのでRouterVuexも入れておきましょう。

? Please pick a preset: 
  default (babel, eslint) 
❯ Manually select features
? Check the features needed for your project: 
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
❯◉ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

firebase初期化

FireStoreモジュールをnpmでインストールします。

npm install firebase

src/plugins/firebase.jsファイルを作成して初期化コードを作成して、export defautしてアプリケーションでfirebaseを利用できるようにします。

import firebase from 'firebase'

if (!firebase.apps.length) {
  firebase.initializeApp(
    {
      apiKey: process.env.VUE_APP_APIKEY,
      authDomain: process.env.VUE_APP_AUTHDOMAIN,
      databaseURL: process.env.VUE_APP_DATABASEURL,
      projectId: process.env.VUE_APP_PROJECTID,
      storageBucket: process.env.VUE_APP_STORAGEBUCKET,
      messagingSenderId: process.env.VUE_APP_MESSAGINGSENDERID,
      appId: process.env.VUE_APP_APPID,
      measurementId: process.env.VUE_APP_MEASUREMENTID,
    }
  )
}

export default firebase

APIキーはクライアントアプリケーションで利用される前提なので、そのまま書いても構いませんが、なんとなく.env.localから読み込みます。

VUE_APP_APIKEY=MY_API_KEY
VUE_APP_AUTHDOMAIN=MY_AUTHDOMAIN
VUE_APP_DATABASEURL=MY_DATABASEURL
VUE_APP_PROJECTID=MY_PROJECT_ID
VUE_APP_STORAGEBUCKET=MY_STORAGE
VUE_APP_MESSAGINGSENDERID=MY_MESSAGER
VUE_APP_APPID=MY_APPID
VUE_APP_MEASUREMENTID=MY_MEMEASUREMENTID

Firebaseの認証機能を提供するモジュールをsrc/plugins/auth.jsに書きましょう。

import firebase from '@/plugins/firebase'

export function login (email, password) {
   return firebase.auth().signInWithEmailAndPassword(email, password)
}

export function logout() {
  return firebase.auth().signOut()
}

export function reAuth(email, password) {
  return firebase.auth.EmailAuthProvider.credential(email, password)
}

export function auth () {
  return new Promise(resolve => {
    firebase.auth().onAuthStateChanged(user => {
      resolve(user || false)
    })
  })
}

メールアドレスとパスワードによるログインを利用するには、firebase.auth().signInWithEmailAndPasswordを利用するので、これをラップした関数をexportします。

ログアウトは前回と同じく、firebase.auth().signOutを利用します。これもラップします。 reAuth関数は後ほど使用します。

ログイン画面

ここからコピペしたログインフォームをちょっといじって利用します。

こんな感じです。 スクリーンショット 20200419 19.01.43.png スクリプト部分このようになっています。 なお、バリデーションやエラー処理などは省いています。

<script>
import { validationMixin } from 'vuelidate'
import { required, email } from 'vuelidate/lib/validators'
import { login } from '@/plugins/auth'

export default {
  data() {
    return {
      email: '',
      password: '',
      redirect: '' // ログイン後にリダイレクトさせたいurl
    }
  },
  // Vuelidateによるバリデーション
  mixins: [validationMixin],
  validations: {
    email: { required, email },
    password: { required },
  },
  methods: {
    onSubmit () {
      this.$v.$touch(
      if (this.$v.$invalid) return
      login(this.email, this.password)
        .then(() => this.$router.push(this.redirect))
        .catch(() => {
          // エラー処理を個々で行う
        })
    },
  },
}
</script>

はじめにインポートしている

import { validationMixin } from 'vuelidate'
import { required, email } from 'vuelidate/lib/validators'

これらは、Vuelidateという軽量バリデーションライブラリです。ここでは詳細は割愛します。

また、先程作成したauthモジュールからlogin関数をインポートしましょう。

import { login } from '@/plugins/auth'

dataプロパティのemailpasswordv-modelによってフォーム入力とバインディングされます。

methodsプロパティのonSubmitメソッドは、submitイベントによって呼び出されます。

バリデーションが通過したなら、login関数を呼び出しましょう。 先程見たとおり、login関数はfirebase.auth().signInWithEmailAndPasswordのラッパー関数です。 firebase.auth().signInWithEmailAndPasswordは、メールアドレスとパスワードを渡すこによって、ログインをすることができます。

firebase.auth().signInWithEmailAndPasswordは、Promiseを返すので、ログインが成功していることを確認できたなら、ログイン後リダイレクトをさせます。

なんらかの理由でログインに失敗しているのなら、(メールアドレスかパスワードが間違っている、ネットワークエラーなど)エラーをキャッチしてエラー処理を行います。

ログインユーザーの取得

現在ログインしているユーザーは、先週と同じくfirebase.auth().onAuthStateChangedで取得します。 これも、authモジュールauth関数によってPromiseを返すようにラップしています。

ユーザープロフィールを取得できます。

import { auth } from `@/plugins/auth`

async () => {
  const user = await auth()
  if (!user) return 
  user.displayName
  user.email
  user.emailVarified
  user.photoURL
  user.uid 
}

ユーザーのプロフィールを更新する

updateProfileメソッドを利用すれば、ユーザーのプロフィールを更新することができます。

import { auth } from `@/plugins/auth`

async () => {
  const user = await auth()
  if (!user) return 
  user.updateProfile({
    displayName: '新しい名前',
    photoURL: 'newPhoto.jpg'
  })
}

メールアドレスを更新する

ユーザーのメールアドレスを更新するには、updateEmailを利用します。 注意しなければいけないところは、メールアドレスやパスワードの更新など、セキュリティ上重要な操作はユーザーが再認証する必要があるところです。

再認証をするためには、まずは新しい認証情報を取得します。

そのためには、firebase.auth.EmailAuthProvider.credentialemailpasswordを渡します。 取得した認証情報をuserメソッドのreauthenticateWithCredentialにわたすことで、再認証が完了します。

これはauthモジュールreAuth関数でラップしてるので、次のように新しい認証情報を取得できます。

取得した

import { reAuth } from '@/src/plugins/auth'

export default {
methods: {
  async onSubmit() {
      try {
        this.loading = true
     // 再認証のためのメールアドレスは、ログインユーザーから取得します。
     // パスワードはフォーム入力から取得します。
        const credential = await reAuth(this.user.email, this.password)
        await this.user.reauthenticateWithCredential(credential)
        await this.user.updateEmail(this.email)
     } catch {
       // エラー処理
   } finnaly { 
       this.loading = false
     }
  }
}

ユーザーのパスワードを更新する

パスワードの更新も、同じく再認証をする必要があります。 userメソッドのupdatePasswordを利用します。

Cloud Storage

次はStorageを利用します。 Cloud Storageは写真や動画など、ユーザーが作成したコンテンツを保管、提供します。 AWSのS3のように使用できます。

Storageの設定を初期化する

storageauthと同じようにモジュールで初期化して利用します。 src/plugins/storage.jsに以下のようにかきます。

import firebase from '@/plugins/firebase'

export const storage = firebase.storage()

Cloud Storageを利用する

実際にStorageを利用してみます。 例として、ブログ作成CMSでサムネイルを設定する機能を作成します。

スクリーンショット 20200419 22.44.31.png

機能として、新しい画像を新たにアップロードしてサムネイルを設定するか、ブログ内で使用した画像をサムネイルに設定できるようにします。

画像をアップロードする

まずは、画像をアップロードしてサムネイルを設定する機能を作成します。

<template>は次のようになっています。

<template>
    <v-dialog v-model="dialog" scrollable max-width="600px">
      <template v-slot:activator="{ on }">
        <v-btn color="primary" class="ma-5" v-on="on">
          <v-icon>fas fa-image</v-icon>
          サムネイルの設定
        </v-btn>
      </template>
      <v-card>
        <v-card-title>サムネイルの設定</v-card-title>
        <v-divider></v-divider>
        <v-progress-linear
          v-model="fileLoading"
          stream
        ></v-progress-linear>
        <v-card-text style="height: 600px;">
          <v-card-subtitle>現在のサムネイル</v-card-subtitle>
          <v-row>
            <v-col cols=4>
              <v-img
                :src="thumbnail"
                alr="サムネイル"
                width=200
                height=200
              ></v-img>
            </v-col>
            <v-col cols=8>
              <v-file-input accept="image/*" label="画像をアップロードして設定する。" @change="onFileUpload"></v-file-input>
              <v-btn color="error" @click="deleteThumbnail">サムネイルを削除する</v-btn>
            </v-col>
          </v-row>

注目すべき点は、<v-file-input>によってファイルがアップロードされたら、onFileUploadイベントが発火するところです。 (<v-file-input><input type="file">を提供するVuetifyのコンポーネントです。)

onFileUploadイベントを見ていきます。

onFileUpload(file) {
    const fileType = this.getFileType(file)
      if (!fileType) {
        this['flash/setFlash']({
          message: 'ファイルタイプが不正です。',
          type: 'error'
        })
      }
      const storageRef = storage.ref(`articles/${this.article.id}/thumbnail.${fileType}`)
      const uploadTask = storageRef.put(file)
      uploadTask.on('state_changed', 
          snapshot => {
            const percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
            this.fileLoading = percentage
          },
          err => {
            console.log(err)
            this['flash/setFlash']({
              message: 'ファイルのアップロードに失敗しました。',
              type: 'error'
            })
          },
          () => {
            uploadTask.snapshot.ref.getDownloadURL().then(downloadURL => {
              this.fileLoading = 0
              this.thumbnail = downloadURL
            })
          }
        )
    },

@changeイベントはfile APIを受け取ります。 getFileTypethis['flash/setFlash']は独自で作成したメソッドです。 ファイルの拡張子を取得する機能と、フラッシュメッセージ機能を提供します。

ファイルの参照を作成

ファイルをCloud Storageにアップロードするには、まずファイル名を含むファイルの完全パスへの参照を作成します。

ファイルの参照を作成するために、storage.ref()メソッドを利用します。

const storageRef = storage.ref(`articles/${this.article.id}/thumbnail.${fileType}`)

ファイルをアップロード

参照を作成したら、storageRef.put()メソッドでファイルをアップロードします。

put()は先程取得したfile APIBlob API経由でファイルを取得し、Cloud Storageにアップロードします。

const uploadTask = storageRef.put(file)

ファイルのアップロード状況を監視する

Cloud Storageでファイルをアップロードしている最中には、アップロード状況を監視することができます。

uploadTask.on('state_changed', 
          snapshot => {
            const percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
            this.fileLoading = percentage
          },
          err => {
            console.log(err)
            this['flash/setFlash']({
              message: 'ファイルのアップロードに失敗しました。',
              type: 'error'
            })
          },
          () => {
            uploadTask.snapshot.ref.getDownloadURL().then(downloadURL => {
              this.fileLoading = 0
              this.thumbnail = downloadURL
            })
          }
        )

進行率を取得するには、転送済みのバイト数(snapshot.bytesTransferred)をアップロード予定のバイト数(snapshot.totalBytes)の合計で割って100をかけて算出します。

2つ目の関数では、エラー処理を担当することになります。

最終的にアップロードが完了した場合には、3つ目の関数が呼び出されます。 アップロードが完了したらgetDownloadURLメソッドから、画像へアクセスするためのURLを取得できます。

このURLを新たにサムネイルプロパティに設定して、データベースに保存したらサムネイルの設定は完了です。

ブログ内で使用した画像をサムネイルに設定できるようにする。

こんどは、ブログ内で使用した画像を取得して設定するよういします。

次のような<template>をです。

          <v-card-subtitle>記事内の画像から設定する。</v-card-subtitle>
          <v-row>
            <v-col cols=12>
              <v-card>
                <.div v-if="loading">
                  画像データの取得中...
                  <v-progress-circular indeterminate color="red"></v-progress-circular>
                </div>
                <div>
                  この記事に画像は使われていません。
                </div>
                <v-container fluid v-else>
                  <v-row>
                    <v-col
                      v-for="(image, index) in images"
                      :key="index"
                      :index="index"
                      class="d-flex child-flex"
                      cols="4"
                    >
                      <v-card flat tile class="d-flex">
                        <v-img
                          :id="index"
                          :src="image"
                          aspect-ratio="1"
                          :class="{ selected: isSelected(index) }"
                          @click="onClick"
                        >
                          <template v-slot:placeholder>
                            <v-row
                              class="fill-height ma-0"
                              align="center"
                              justify="center"
                            >
                              <v-progress-circular indeterminate color="grey lighten-5"></v-progress-circular>
                            </v-row>
                          </template>
                        </v-img>
                      </v-card>
                    </v-col>
                  </v-row>
                </v-container>
              </v-card>
            </v-col>
          </v-row>

images配列をv-forでループして、使用した画像一覧を表示します。 images配列に画像を渡しましょう。

画像のリストをダウンロードする

日付が変わりそうなので今週はここまで

この記事をシェアする
Twitterで共有
Hatena

関連記事