masalibの日記

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

React Firebase入門 Twitter認証(既存ユーザー向け)と解除

メールアドレス認証してくれたユーザーに対してもTwitter認証できるようにしたいと思います。 色々なサービスで標準である機能なので作れれてよかった

この記事は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認証(既存ユーザー向け)と解除 今ここ
2-1・ FirebaseStorageのファイルアップ:基礎
2-2・ FirebaseStorageのファイルアップ前に画像の切り抜き
2-3・ FirebaseStorageのファイルアップの移動
3-1・FirestoreのCRUD
3-2・Firestoreのデータ取得補足
3-3・Firestoreのページネーション処理
3-4・Firestoreのコレクション(テーブル)のJOIN
よかったら他の記事も見てください。

やりたい事

  • メールアドレス認証したユーザーに対してTwitter認証を追加する
  • Twitter認証を解除する

プログラムについて

修正箇所が多いので全部みたい人はブランチのリンクから参照してください

github.com

プロフィールの更新する画面の修正箇所

Twitterの処理用のファイルを作成

/src/firebaseprovider/Twitter.js

import React, { useState, useEffect } from "react";
import { useAuth } from "../../contexts/AuthContext"
import  firebase, {Twitter} from "../../firebase"
import { useHistory} from "react-router-dom"
import TwitterIcon from '@material-ui/icons/Twitter';
import { Button,} from '@material-ui/core';
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";

const useStyles = makeStyles((theme: Theme) =>
    createStyles({
        twitterBtn: {
            flexGrow: 1
        },
        twitterloginBtn: {
            marginTop: theme.spacing(2),
            flexGrow: 1
        },
    })
);

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

export const TwitterLink = () => {
    const classes = useStyles();//Material-ui
    const [isTwitterLink, setIsTwitterLink] = useState(false)
    const [twitterMessage, setTwitterMessage] = useState("")
    const { currentUser, } = useAuth() 
    const history = useHistory()

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

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

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


    return (
        <>
            {twitterMessage && <div style={{ color: "red" }}>{twitterMessage}</div>}
            {isTwitterLink && 
                <>
                    <TwitterIcon />Twitter:{' '}{' '}{' '}認証されています
                    <Button
                        fullWidth
                        variant="contained"
                        size="large"
                        color="primary"
                        className={classes.twitterBtn}
                        onClick={handleTwitterUnLink}
                        >
                        認証を解除する
                    </Button>
                </>
            }
            {!isTwitterLink && 
                <>
                    <TwitterIcon />Twitter:{' '}{' '}{' '}認証されていません
                    <Button
                        fullWidth
                        variant="contained"
                        size="large"
                        color="primary"
                        className={classes.twitterBtn}
                        onClick={handleTwitterLinkWithPopup}
                        >
                        >認証する
                    </Button>
                </>
            }
        </>
    )
}

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

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

解説

currentUser.linkWithPopup(Twitter).then(function(result) {

は既存のユーザーに対して新しいプロバイダー(Twitter)を追加しています。 ログインと同じようにポップアップが表示されてGoogle先生がいい感じにやってくれます

currentUser.unlink("twitter.com").then(function() {

はユーザーのプロバイダーとのリンクを解除しています。 resultがないのがちょっと不安なんだけど・・・errorならエラー処理にいくのでいいかな

結果

  1. 認証されていない状態
    f:id:masalib:20201211055142p:plain
  2. 認証ボタンを押したあとの状態
    f:id:masalib:20201211055155p:plain
  3. Firebaseのコンソール画面 f:id:masalib:20201211055207p:plain

できていない事

  • 1度ログインするとツイッターの認証画面が省略されてしまいます。複数のアカウントがある人むけに クリアしないといけないんですが。。。よくわからず 処理はいれてみたのですが・・・複数表示される事はなかった
Twitter.setCustomParameters({
    prompt: 'select_account', // 追加
});

qiita.com

googleの認証を追加するときに検証しようと思う

  • Twitter認証しかしていない人が認証をはずすと匿名アクセスになってしまう。このままログアウトになるとアクセスできなくなる。本番運用する場合は認証をはずすときに他のプロバイダーが登録されているのかをチェックするべきなんだけど・・・まだできていない

感想

既存のユーザーのリンク機能も追加された。メールアドレスとパスワードを入力しなくても済むので楽になった