masalibの日記

システム開発、運用と猫の写真ブログです

React Firebase入門 Google認証(既存ユーザーも含む)と解除

Google先生のアカウントをもっていない人なんてめったにいない。 Googleの認証ができれば新規の会員登録の障壁がへる。この機能は今後のWEBアプリの基本となると思うのでできてよかった。

この記事はReact Firebase入門シリーズです 1-1-1・ Firebase初期設定とFirebaseAuthのSignUp
1-1-2・ 「react-hook-form」を入れてみた
1-1-3・ AuthのSignUpの通信エラー対応
1-2 ・ FirebaseAuthのログイン処理
1-3 ・ FirebaseAuthのログイン認証とログアウト処理
1-4 ・ FirebaseAuthのパスワード初期化処理
1-5 ・ FirebaseAuthのメールアドレスの有効化
1-6 ・ FirebaseAuthのメールアドレスとパスワード変更
1-7 ・ FirebaseAuthの表示名変更
1-8 ・ FirebaseAuthの拡張項目追加
1-9-1 ・ FirebaseAuthのTwitter認証
1-9-2 ・ FirebaseAuthのTwitter認証(既存ユーザー向け)と解除
1-10 ・ FirebaseAuthのGoogle認証(既存ユーザー含む)と解除 今ここ
2-1・ FirebaseStorageのファイルアップ:基礎
2-2・ FirebaseStorageのファイルアップ前に画像の切り抜き
2-3・ FirebaseStorageのファイルアップの移動
3-1・FirestoreのCRUD
3-2・Firestoreのデータ取得補足
3-3・Firestoreのページネーション処理
3-4・Firestoreのコレクション(テーブル)のJOIN
よかったら他の記事も見てください。

やりたい事

  • Google認証のSingUP,ログインをする
  • メールアドレス認証している人にGoogle認証を追加する
  • Google認証を解除する

事前作業

FirebaseのAuthGoogleのプロバイダーを有効化します

f:id:masalib:20201211232545p:plain

FirebaseのWebコンソールへログインしたら、メニュー「Authentication」→「Sign-in method」→「Google」→「編集」とたどります。

f:id:masalib:20201211232836p:plain

有効化にする
これだけです。Twitterと違い簡単です。

プログラムについて

Googleの認証のプロバイダーを追加する。Twitterとそこまで変わらない /src/firebase.js

export const Twitter = new firebase.auth.TwitterAuthProvider();
+ export const Google = new firebase.auth.GoogleAuthProvider();
export default firebase;

Twitterと同様にコンポーネントを作ります。ほぼ同じなのですが、もしかしてOAuthのスコープを指定する事があるかもしれないので分けて作りました

/src/firebaseprovider/Google.js

iimport React, { useState, useEffect } from "react";
import { useAuth } from "../../contexts/AuthContext"
import  firebase, {Google} from "../../firebase"
import { useHistory} from "react-router-dom"
import { Button,} from '@material-ui/core';
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faGoogle } from "@fortawesome/free-brands-svg-icons";

const useStyles = makeStyles((theme: Theme) =>
    createStyles({
        googleBtn: {
            flexGrow: 1
        },
        googleloginBtn: {
            marginTop: theme.spacing(2),
            flexGrow: 1
        },
        googleIcon: {
            fontSize: "1.5rem",
        },
    })
);

export const GoogleSingUpLogin = (props) => {
    const classes = useStyles();//Material-ui
    const history = useHistory()
    const [googleMessage, setGoogleMessage] = useState("")
    async function handleGoogleSignup (event) {  
        console.log("handleGoogleSignup")
        Google.setCustomParameters({
            prompt: 'select_account', // 追加
        });
        setGoogleMessage("認証中")
        firebase
            .auth()
            .signInWithPopup(Google)
            .then((result) => {
                console.log(result);
                setGoogleMessage("認証に成功しました。ダッシュボードにリダレクトします")
                setTimeout(function(){
                    console.log("リダレクト処理")
                    history.push("/dashboard")
                },1000);
            }).catch(function(error) {
                switch (error.code) {
                    case "auth/network-request-failed":
                        setGoogleMessage("通信がエラーになったのか、またはタイムアウトになりました。通信環境がいい所で再度やり直してください。");
                        break;
                    case "auth/credential-already-in-use": 
                        setGoogleMessage("他のユーザーでGoogle認証しているため、認証ができませんでした。");
                        break;
                    case "auth/user-disabled": 
                        setGoogleMessage("入力されたアカウントまたはメールアドレスは無効(BAN)になっています。");
                        break;
                    case "auth/requires-recent-login": 
                        setGoogleMessage("別の端末でログインしているか、セッションが切れたので再度、ログインしてください。(ログインページにリダイレクトします)");
                        setTimeout(function(){
                            console.log("リダレクト処理")
                            history.push("/login")
                        },3000);
                        break;
                    default:  //想定外
                        setGoogleMessage("失敗しました。通信環境がいい所で再度やり直してください。");
                }
            });
    }
    return (
        <>
            {googleMessage && <div style={{ color: "red" }}>{googleMessage}</div>}
            <Button
                fullWidth
                variant="contained"
                size="large"
                color="primary"
                className={classes.googleloginBtn}
                onClick={handleGoogleSignup}
            >
                <FontAwesomeIcon className={classes.googleIcon}  icon={faGoogle} />{props.title}
            </Button>
        </>
    )
}

export const GoogleLink = () => {
    const classes = useStyles();//Material-ui
    const [isGoogleLink, setIsGoogleLink] = useState(false)
    const [googleMessage, setGoogleMessage] = useState("")
    const { currentUser, } = useAuth() 
    const history = useHistory()

    useEffect(() => {
        async function fetchData() { 
            //providerData Linkチェック
            setIsGoogleLink(false)
            currentUser.providerData.forEach(element => {
                if (element.providerId === "google.com" ){
                    setIsGoogleLink(true)
                }
            });
        }
        fetchData();
    },[currentUser]);

    function handleGoogleUnLink () {  
        console.log("handleGoogleUnLink")
        setGoogleMessage("")
        currentUser.unlink("google.com").then(function() {
            setGoogleMessage("Googleとのリンクを解除しました")
            setIsGoogleLink(false)
        }).catch(function(error) {
            console.log(error)
            switch (error.code) {
                case "auth/network-request-failed":
                    setGoogleMessage("通信がエラーになったのか、またはタイムアウトになりました。通信環境がいい所で再度やり直してください。");
                    break;
                case "auth/credential-already-in-use": 
                    setGoogleMessage("他のユーザーでGoogle認証しているため、認証ができませんでした。");
                    break;
                case "auth/requires-recent-login": 
                    setGoogleMessage("別の端末でログインしているか、セッションが切れたので再度、ログインしてください。(ログインページにリダイレクトします)");
                    setTimeout(function(){
                        console.log("リダレクト処理")
                        history.push("/login")
                    },3000);
                    break;
                default:  //想定外
                    setGoogleMessage("失敗しました。通信環境がいい所で再度やり直してください。");
            }
        });
    }

    async function handleGoogleLinkWithPopup (event) {  
        console.log("handleGoogleLinkWithPopup")
        setGoogleMessage("")
        Google.setCustomParameters({
            prompt: 'select_account', // 追加
        });
        currentUser.linkWithPopup(Google).then(function(result) {
            console.log("handleGoogleLinkWithPopup:result",result)
            setGoogleMessage("Googleとリンクしました")
            setIsGoogleLink(true)
        }).catch(function(error) {
            // Handle Errors here.
            switch (error.code) {
                case "auth/network-request-failed":
                    setGoogleMessage("通信がエラーになったのか、またはタイムアウトになりました。通信環境がいい所で再度やり直してください。");
                    break;
                case "auth/credential-already-in-use": 
                    setGoogleMessage("他のユーザーでGoogle認証しているため、認証ができませんでした。");
                    break;
                case "auth/requires-recent-login": 
                    setGoogleMessage("別の端末でログインしているか、セッションが切れたので再度、ログインしてください。(ログインページにリダイレクトします)");
                    setTimeout(function(){
                        console.log("リダレクト処理")
                        history.push("/login")
                    },3000);
                    break;
                default:  //想定外
                    setGoogleMessage("失敗しました。通信環境がいい所で再度やり直してください。");
            }
            console.log(error)
            // ...
        });
    }


    return (
        <>
            {googleMessage && <div style={{ color: "red" }}>{googleMessage}</div>}
            {isGoogleLink && 
                <>
                    <FontAwesomeIcon className={classes.googleIcon}  icon={faGoogle} />Google:{' '}{' '}{' '}認証されています
                    <Button
                        fullWidth
                        variant="contained"
                        size="large"
                        color="primary"
                        className={classes.googleBtn}
                        onClick={handleGoogleUnLink}
                        >
                        認証を解除する
                    </Button>
                </>
            }
            {!isGoogleLink && 
                <>
                    <FontAwesomeIcon className={classes.googleIcon}  icon={faGoogle} />Google:{' '}{' '}{' '}認証されていません
                    <Button
                        fullWidth
                        variant="contained"
                        size="large"
                        color="primary"
                        className={classes.googleBtn}
                        onClick={handleGoogleLinkWithPopup}
                        >
                        認証する
                    </Button>
                </>
            }
        </>
    )
}

あとはSignUPやログイン、プロフィール画面からコンポーネントを読んでいるだけです

SignUp
/src/Signup.js

<TwitterSingUpLogin title="Twitterでアカウント作成"/>
+ <GoogleSingUpLogin title="Googleでアカウント作成"/>

ログイン
/src/Login.js

<TwitterSingUpLogin title="TwitterでLoginする"/>
+ <GoogleSingUpLogin title="GoogleでLoginする"/>

プロフィール画面
/src/UpdateProfile.js

<Typography className={classes.subtitle2} variant="subtitle2"> 外部アプリケーション認証</Typography>
<TwitterLink />
+ <GoogleLink />

結果

  1. SingUpの画面
  2. ボタンを押したあとの状態
  3. Firebaseのコンソール画面

結果詳細

  • ログインが複数選ぶ事ができる。Twitterではできなかったんですが、Googleだとできるみたい。理由がわからないけど・・・
  • Google認証で取得されたメールアドレスは有効化されていた
  • アバターGoogleに登録されている画像URLが設定されていた f:id:masalib:20201211233243p:plain
  • パスワードは設定されていなかった。パスワードを入力するとFirebaseのコンソールでメールのアイコンが追加された。
    f:id:masalib:20201211233714p:plain
    プロバイダーはpasswordだった・・・
    f:id:masalib:20201211233955p:plain

できていない事

  • OAuthのスコープを変更できるだけど。。。よくわからず・・・今の所は使わないので放置

省略可: 認証プロバイダにリクエストする追加の OAuth 2.0 スコープを指定します。スコープを追加するには、addScope を呼び出します。次に例を示します。
provider.addScope('https://www.googleapis.com/auth/contacts.readonly'); https://developers.google.com/identity/protocols/oauth2/scopes

感想