Firebase Authentication を利用した認証システムの実装


## ２０２２年２月追記

Firebase SDK v9では、実装方法が大きく変わりました。
v9を利用した認証の実装については、以下をご覧ください。

https://qiita.com/TakeshiNickOsanai/items/32d99dba391e81005dd1

## Authenticationを利用した実装


[前回](https://qiita.com/TakeshiNickOsanai/items/838c0f15c10a2270a109) に続き、 Firebase の備忘録を書いていきます。
今回はFirebase Authenticationを利用した認証システムの実装についてメモを残しておきます。


## Firebase Authentication の設定

認証は、Firebase プロジェクトのコンソールから「Auhentication」を選んで設定していきます。

公式ドキュメントは以下。

https://firebase.google.com/docs/auth


ここでは、サンプルアプリで利用したGoogle 認証に絞って記述します。


## Google 認証の設定

Authentication →　Sign-in methodを選び、設定を「有効」に変更→保存で、終了。



## Google認証の実装

Firebase SDKを利用します。

前項で書いた通り、認証用のSDKと構成オブジェクトのロードを済ませておきます。

```javascript
  <script src="/__/firebase/7.16.0/firebase-app.js"></script> 
  <script src="/__/firebase/7.16.0/firebase-auth.js"></script> 
  <script src="/__/firebase/init.js"></script> 
```

実装方法は大まかに言うと

・Firebase UIの利用
・自前開発

の2つを選べます。

## Firebase UIの利用

公式ドキュメントは [こちら](https://firebase.google.com/docs/auth/web/firebaseui
)。

https://firebase.google.com/docs/auth/web/firebaseui

基本的な流れは

・Firebase UI用のJSファイルとCSSファイルをロード
・Firebase UIをウェブアプリ内で初期化
・ウェブアプリ内のDOMに表示

となります。

詳しい流れは公式ドキュメントの通りでOK。
https://firebase.google.com/docs/auth/web/firebaseui#before_you_begin

最初に、Firebase UI用のJSファイルとCSSファイルをロードします。ライブラリはhead要素内で読み込むように指定するようです。


```javascript
<head>
  <title>Firebase Authentication sample 01</title>
  <script src="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.js"></script>
  <link href="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.css" rel="stylesheet" type="text/css">
</head>
```

## 初期化

Googleを利用する場合の初期化は以下に記述されています。

https://firebase.google.com/docs/auth/web/firebaseui#oauth_providers_google_facebook_twitter_and_github

サンプルコードではGoogle以外に、FacebookやTwitterなど、別な認証用の記述があります。今回のサンプルの様に、Googleによる認証のみ行う場合、他の手段は削除してOK。

サンプルコードでは「#firebaseui-auth-container」と言うIDのDOMを指定しているが、DOMの指定はなんでも良い。


## ログインチェック

[onAuthStateChanged](https://firebase.google.com/docs/auth/web/start#set_an_authentication_state_observer_and_get_user_data
)メソッドを使います。
https://firebase.google.com/docs/auth/web/start#set_an_authentication_state_observer_and_get_user_data


## サインアウト
[サインアウトメソッド](https://firebase.google.com/docs/auth/web/google-signin#next_steps
)を実行するだけです。簡単。

https://firebase.google.com/docs/auth/web/google-signin#next_steps

```javascript

firebase.auth().signOut().then(function() {
  // Sign-out successful.
}).catch(function(error) {
  // An error happened.
});
```

## サンプルコード

これらを踏まえて、サンプルコードを実装してみました。仕様は以下の通り。

* Google を利用して認証する
* Firebase Authentication UIを利用して実装。
* 初期画面では、Googleのサインインボタンを表示
* サインインすると、Google ユーザーのユーザー名をウェルカムメッセージとともに表示する。
* サインアウト すると、Authentication UIの初期画面に戻る。

実際に動くサンプルは [こちら](https://todomanage.web.app/sample/auth-sample01.html) で公開しています。
https://todomanage.web.app/sample/auth-sample01.html

サンプルコード
auth-sample01.html

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Firebase Authentication sample 01</title>
  <script src="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.js"></script>
  <link href="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.css" rel="stylesheet" type="text/css">
</head>
<body>
  <h1>Firebase Authenction Sample 01</h1>
  <div id="auth"></div>
  <script src="/__/firebase/7.16.0/firebase-app.js"></script> 
  <script src="/__/firebase/7.16.0/firebase-auth.js"></script> 
  <script src="/__/firebase/init.js"></script> 
  <script type="text/javascript">

    /*
  Firebase Authentcation を使った認証サンプル 01
  Firebase Authentication UI を使い、サインイン用のUIを生成
  ポップアップウィンドウで認証画面を表示、サインイン後元の画面に移動
  サインアウト ボタンと、Googleの表示名をウェルカムメッセージとして表示
  サインアウト 用の関数「signOut」を定義
 
  */

  const ui = new firebaseui.auth.AuthUI(firebase.auth());
  const uiConfig = {
  callbacks: {
    signInSuccessWithAuthResult: function(authResult, redirectUrl) {
      return true;
    },
  },
  signInFlow: 'popup',
  signInSuccessUrl: 'auth-sample01.html',
  signInOptions: [
    firebase.auth.GoogleAuthProvider.PROVIDER_ID,
  ],
  tosUrl: 'sample01.html',
  privacyPolicyUrl: 'auth-sample01.html'
  };

  ui.start('#auth', uiConfig);

  firebase.auth().onAuthStateChanged(user => {
    if (user) {
        const signOutMessage = `
        <p>Hello, ${user.displayName}!<\/p>
        <button type="submit"  onClick="signOut()">サインアウト<\/button>
        `;
        document.getElementById('auth').innerHTML =  signOutMessage;
        console.log('ログインしています');

    } 
  });

  function signOut() {
  firebase.auth().onAuthStateChanged(user => {
    firebase
      .auth()
      .signOut()
      .then(() => {
        console.log('ログアウトしました');
        location.reload();
      })
      .catch((error) => {
        console.log(`ログアウト時にエラーが発生しました (${error})`);
      });
  });
  }

  </script>
</body>
</html>
```

## 独自UIで開発

基本はFirebase UIと変わらないです。

* Firebase Authentication を使ってAuthオブジェクトを作成
* 認証用のメソッドを呼び出し

となります。詳細は以下。

https://firebase.google.com/docs/auth/web/google-signin#handle_the_sign-in_flow_with_the_firebase_sdk


## Google プロバイダ オブジェクトの作成

Firebase Authentication のSDKをロード後、プロバイダオブジェクトを生成します。

```javascript

var provider = new firebase.auth.GoogleAuthProvider();
```

サインイン用のメソッドを呼び出す。

```javascript

firebase.auth().signInWithPopup(provider).then(function(result) {

 // 認証後の処理を記述します

}).catch(function(error) {

 // エラー時の処理を記述します

});
```

認証用のメソッドはここにまとまっています。

https://firebase.google.com/docs/reference/js/firebase.auth.Auth


## 認証後のユーザー情報

認証後のユーザー情報は「user」もしくは「userIInfo」メソッドで取得できます。

https://firebase.google.com/docs/reference/js/firebase.User
https://firebase.google.com/docs/reference/js/firebase.UserInfo

```javascript

firebase.auth.user
firebase.auth.userinfo
```

## サンプルコード

Authentication UI の時と同様、サンプルコードを書いてみました。仕様は以下の通りです。

* ボタンを押すと、ポップアップウィンドウが表示され、Google で認証できる
* 認証が成功したら、現在サインインしたユーザーの名前とメールアドレスがウェルカムメッセージとともに表示される
* サインアウト ボタンを押すと初期状態に戻る


動作サンプルは [こちら](https://todomanage.web.app/sample/auth-sample02.html)。

https://todomanage.web.app/sample/auth-sample02.html

サンプルコード
auth-sample02.html

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Firebase Authentication sample 02</title>
  <script src="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.js"></script>
  <link href="https://cdn.firebase.com/libs/firebaseui/3.5.2/firebaseui.css" rel="stylesheet" type="text/css">
</head>
<body>
  <h1>Firebase Authenction Sample 02</h1>
  <div id="auth"></div>
  <script src="/__/firebase/7.16.0/firebase-app.js"></script> 
  <script src="/__/firebase/7.16.0/firebase-auth.js"></script> 
  <script src="/__/firebase/init.js"></script> 
  <script type="text/javascript">

  /*
  Firebase Authentcation を使った認証サンプル 02
  Google認証用のオブジェクトを生成
  認証用の関数「signIn」を定義
  サインアウト 用の関数「signOut」を定義
  認証の状態をチェックして、「サインイン」「サインアウト 」のボタンを出し分ける
  */

  const provider = new firebase.auth.GoogleAuthProvider();

  function signIn() {
  firebase.auth().signInWithPopup(provider)
  .then(result => {
      console.log('ログインしました。');

  }).catch(error => {
        const signinError = `
        サインインエラー
        エラーメッセージ： ${error.message}
        エラーコード: ${error.code}
        `
  　　　console.log(signinError);
  });
  }

  function signOut() {
  firebase.auth().onAuthStateChanged(user => {
    firebase
      .auth()
      .signOut()
      .then(() => {
        console.log('ログアウトしました');
        location.reload();
      })
      .catch((error) => {
        console.log(`ログアウト時にエラーが発生しました (${error})`);
      });
  });
  }

  firebase.auth().onAuthStateChanged(user => {
    if (user) {
        const signOutMessage = `
        <p>Hello, ${user.displayName}!<\/p>
        <button class="btn btn-primary" type="submit"  onClick="signOut()">サインアウト<\/button>
        `;
        document.getElementById('auth').innerHTML =  signOutMessage;
        console.log('ログインしています');

    } else {
          const signInMessage = `
          <button class="btn btn-primary" type="submit"  onClick="signIn()">サインイン<\/button>
          `;
          document.getElementById('auth').innerHTML = signInMessage;       
    }
  });

  </script>
</body>
</html>
```


## ユーザー情報取得のラグについて

Auhenticationで認証後、ウェブアプリでユーザー情報を取得しようとしてうまくいかない場合があります。

以下は、Firebaseのドキュメントに記述されている認証チェックのコードです。


```javascript

var user = firebase.auth().currentUser;

if (user) {
  // User is signed in.
} else {
  // No user is signed in.
}

```

認証が完了して、サインインが済んでいる状態でこのコードを使うと、認証中のユーザー情報「currentUser」の情報をもとに色々な処理が行えます。

一方で、サインイン直後にこのコードを使おうとすると、オブジェクト生成が完了する前に処理が走り、エラーが発生するケースが多い。

これを避けるためには[「onAuthStateChanged」](https://firebase.google.com/docs/auth/web/manage-users?hl=ja#get_the_currently_signed-in_user)でユーザーの状態をチェックしてから処理するとうまくいきます。

https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onauthstatechanged


認証が成功していれば、onAuthStateChanged メソッドからユーザーデータが含まれたオブジェクトが返るため、このデータを元にゴニョゴニョします。

```javascript

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    // User is signed in.
  }
});
```


自分はPromiseオブジェクトを生成して、ユーザーオブジェクトをresolveしてからゴニョゴニョしてみました。

```javascript

// Firebase Authentication のPromiseオブジェクトを生成する関数を定義

    function genUser() {
    return new Promise((resolve, reject) => {
      firebase.auth().onAuthStateChanged(user => {
        if (user) {
          resolve(user);
        } else {
          return null;
        }
      });
    });
  }

// Firebase の認証情報をResolveして処理ロジックに渡す

    Promise.resolve(genUser()).then(user =>{

    // user 内にGoogle認証を利用したユーザー情報が格納されている

    console.log(user.uid);
    console.log(user.displayName);

    });

```