Firebaseでログイン機能を作ってみる

# やったこと
FireBaseを使ってログイン機能を作りました。
（ただし、あらかじめ登録してあるものだけログイン可）

最後にコードも貼り付けます。

# 使っているもの
- FireBase
    - https://firebase.google.com/?hl=ja
- Bootstrap
    - https://getbootstrap.com/
- Vue.js , JavaScript , Html 

# できること（サンプル）
- FirebaseのAuthenticationであらかじめ登録したユーザでログインができます
- ログインするとFirebaseのDatabaseにタイムスタンプが送られます
- ログイン時のみナビゲーションヘッダーを出すようにしています

サンプル：
https://simasima.work/index.html


テストユーザアカウント
- メール ： test@test.com 
- PW : test0000

こちらでテスト的にログインできるはずです。
※ 何のお知らせなくログインできなくなることもあるかも知れませんがご了承ください。

### ログイン前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/ba28013c-3864-7146-e42b-32b5f5d5f8c4.png)

### ログイン後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/32b366c5-73d5-8f75-0208-6fe6edf1fe6c.png)



# まずFireBaseのプロジェクトを作成する
下記記事を参考にして、[「1個のアプリ」と表示されたことを確認　⇒　左メニューから各機能の設定を行う](https://qiita.com/yoshi0518/items/25af102845ba05545f98#1%E5%80%8B%E3%81%AE%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A8%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%9F%E3%81%93%E3%81%A8%E3%82%92%E7%A2%BA%E8%AA%8D%E5%B7%A6%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC%E3%81%8B%E3%82%89%E5%90%84%E6%A9%9F%E8%83%BD%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E8%A1%8C%E3%81%86)までやります。

下記画像にあるスクリプトはすぐ使うので控えておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/548c55b7-c214-4855-fe9a-5207ab739dc5.png)


参考：[Firebaseプロジェクト作成方法](https://qiita.com/yoshi0518/items/25af102845ba05545f98)

# Authenticationでログインアカウントを作る
ログインを許可するアカウントを作ります。

まず、Authentication を選択して、Sign-in methodを選択する。
そして、メール/パスワード を有効にする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/cd98ac0f-1c58-1d3a-f322-78bc474e9a27.png)

次に、Usersを選択する（Sign-in methodの隣にある）。
そして、「ユーザを追加」からログインさせたいメールアドレスとパスワードを登録する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/7065ccba-0bca-9da2-33b2-140082f75d6d.png)


# Databaseの設定をする
左ナビのDatabaseを選択して「データベースの作成」をする。
本番かテストモードかを選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/7477c1ea-b3f1-4b4c-6c31-752fa6f6ecf3.png)

ロケーションも設定したら完了をクリックする。

完了したら、「コレクションを開始」をクリックする。
ソースコード上はmemosというコレクションIDでやっているので、コピペで使いたい場合はコレクションIDをメモにする。

ここには下図のようにフィールドを設定しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121887/668d299e-3d62-671f-1c47-39ff8fe781b3.png)


準備しておくべきことは以上で完了です！

# ソースコード

画像のリンクや、Firebaseの情報を入れ変えることで動くかと思います。
ナビゲーションヘッダーのリンク先も適宜変更してください。

```.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Email/Password Authentication Example</title>

  <!-- Load required Bootstrap and BootstrapVue CSS -->
  <link rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
  <link rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />

  <!-- Load polyfills to support older browsers -->
  <script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>

  <!-- Load Vue followed by BootstrapVue -->
  <script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
  <script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>

  <!-- Load the following for BootstrapVueIcons support -->
  <script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue-icons.min.js"></script>

</head>

<body>

  <div class="container">

    <div id="appFirebaseLogin">

    <div v-if="loginStatus">
        <nav class="navbar navbar-expand-lg navbar navbar-dark bg-dark" > 
            <a class="navbar-brand">Menu</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNavDropdown">
              <ul class="navbar-nav">
                <li class="nav-item active">
                  <a class="nav-link" href="./index.html">Home <span class="sr-only">(current)</span></a>
                </li>
                <li class="nav-item">
                  <a class="nav-link" href="./contents/face-emotion.html">感情分析</a>
                </li>
                <li class="nav-item">
                  <a class="nav-link" href="./contents/ocr-read.html">OCR機能</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="./contents/dog.html">ワンちゃん検索</a>
                  </li>
              </ul>
            </div>
          </nav>
        </div>

      <div class="row" v-if="loginStatus">
        <div class="col">
          <img class="rounded mx-auto d-block" src="xxxxxxxxx" width="400" height="auto" vspace="30">
        </div>
      </div>

      <div class="row" v-else>
        <div class="col">
          <img class="rounded mx-auto d-block" src="xxxxxxxxx" width="400" height="auto" vspace="30">
        </div>
      </div>
  
      <div class="row">
        <div class="col">
          
          <div v-if="loginStatus">
            <div class="row">
              <div class="col-6">
                <div class="form-group">
                  ようこそ、{{ userName }} さん！ 
                </div>
              </div>
              <div class="col-6">
                <div class="form-group">
                  <button class="btn btn-primary" v-on:click="handlerLogout">Logout</button>
                </div>
              </div>
            </div>
          </div>
  
          <div v-else>
              <div class="form-group">
                <label>Email</label>
                <input v-model="email" class="form-control"/>
              </div>
              <div class="form-group">
                <label>Password</label>
                <input v-model="password" type="password" class="form-control">
              </div>
              <div class="form-group">
                <button class="btn btn-primary" v-on:click="handlerLogin">Login</button>
              </div>
          </div>
  
        </div>
      </div>
    </div>

  </div>

  <!-- FireBaseの使う機能を指定する -->
    <!-- https://firebase.google.com/docs/web/setup?hl=ja#available-libraries -->
    <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-auth.js"></script>
    <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-firestore.js"></script>
    <script>
        // ここは自分のアカウントう情報
        // Your web app's Firebase configuration
        var firebaseConfig = {
            apiKey: "xxxxxxxxxxxxxxx",
            authDomain: "xxxxxxxxxxxxxxx",
            databaseURL: "xxxxxxxxxxxxxxx",
            projectId: "xxxxxxxxxxxxxxx",
            storageBucket: "xxxxxxxxxxxxxxx",
            messagingSenderId: "xxxxxxxxxxxxxxx",
            appId: "xxxxxxxxxxxxxxx",
            measurementId: "xxxxxxxxxxxxxxx"
        };
        // Initialize Firebase
        firebase.initializeApp(firebaseConfig);
        // firestoreのインスタンス
        const db = firebase.firestore();
    </script>


<script>
  const app = new Vue({
    el: '#appFirebaseLogin',
    data: {
      email: '',
      password: '',
      loginStatus: false,
      userName:'',
      userID:'',
      message:'',
      items:[]
    },
    methods: {

            /////////////////// Firebase Authentication ///////////////////

            // ログアウトボタンの挙動
            handlerLogout: async function () {
            console.log('handlerLogout');
            await firebase.auth().signOut();
            await this.checkAuthStateChanged();
            // データ更新を監視するリスナーを削除する Cloud Firestore 用の処理
            this.endSnapShot();
            },

            // ログインボタンの挙動
            handlerLogin: async function () {
            console.log('handlerLogin');

            // DBにログイン日時を書き込む処理
            this.handlerAddMemo();

            try {
                const resSignInWithEmailAndPassword = await firebase.auth().signInWithEmailAndPassword(this.email, this.password);
                console.log(resSignInWithEmailAndPassword);
            } catch(error) {
                console.log(error);
                const errorCode = error.code;
                const errorMessage = error.message;
                if (errorCode === 'auth/wrong-password') {
                alert('Wrong password.');
                } else {
                alert(errorMessage);
                }
            }

            
            // ログイン処理後 Firebase Authentication 認証状態のチェック
            this.checkAuthStateChanged();
            

            },

            checkAuthStateChanged: async function(){
            
            // 認証状態の変更を取得
            let user = await this.promiseAuthStateChanged();



            if (user) {
                // ログイン済み
                console.log('ログイン済み');
                // Vue に値を反映
                this.loginStatus = true;    // ログイン状況
                this.userName = user.email; // ユーザー名（メールアドレス）
                this.userID = user.uid;     // Firebase で管理されているユニークなユーザーID
                // データ更新を監視するリスナーに登録する Cloud Firestore 用の処理
                this.startSnapShot();

            } else {
                // 未ログイン
                console.log('未ログイン');
                this.loginStatus = false;  // ログイン状況
            }

            },

            promiseAuthStateChanged: async _ => {
            return new Promise( (resolve, reject) => {
                const f_auth = firebase.auth();
                f_auth.onAuthStateChanged( user => {
                resolve(user);
                });
            });

            },

      /////////////////// Cloud Firestore ///////////////////

        // Cloud Firestore用。これを呼ぶとDBにログイン日時が書き込まれる
        handlerAddMemo: async function() {
            console.log('handlerAddMemo');

            // ここの中にuser情報をとってくる。
            const data = {
                message: this.message,
                timestamp: firebase.firestore.FieldValue.serverTimestamp()
            };

            try {
            await db.collection("memos").doc().set(data);
            console.log("Document successfully written!");
            } catch(error) {
            console.log(error);
            console.error("Error writing document: ", error);
            }
        },

        // データ更新を監視するためリスナーに登録する
        startSnapShot: function(){
            console.log('常に監視するためリスナーに登録する');
            this.unsubscribe = db.collection('memos').orderBy('timestamp', 'desc').onSnapshot(this.listenerSnapShot);
        },

        // データ更新を監視するリスナーを削除する
        endSnapShot: function(){
            console.log('常に監視するリスナーを削除する');
            this.unsubscribe();
        },

        //データ更新が行われた場合に動作する
        listenerSnapShot: function(querySnapshot){
            //console.log('listenerSnapShot');
            this.items = [];
            const _this = this;

            querySnapshot.docs.map(function(doc){
            const _data = doc.data({serverTimestamps: 'estimate'});
            let timestampDate = new Date();
            timestampDate.setTime(_data.timestamp.seconds * 1000);
            
            console.log(_this.userName)
            _this.items.push({
                message:_data.message,
                dateString:timestampDate.toString(),
                timestamp:_data.timestamp

            });
            });
        }

            }
            ,
            mounted() {
            console.log('mounted');
            
            /////////////////// Firebase Authentication ///////////////////
            // はじめて表示されたときの Firebase Authentication 認証状態のチェック
            this.checkAuthStateChanged();
}
  })
</script>


</body>
</html>

```