Firebaseに作成したアプリにログインするためのReactのコードを解説

ログイン プログラミング

Firebaseに作成したReactのアプリにログインする処理にフォーカスを当てて解説します。

※ ソースはこちら:Github

※ アプリはこちら:「Firebaseに認証するだけのアプリ」

※ パッケージのバージョン:”firebase”: “^9.22.1″、”react”: “^18.2.0″ (詳細はGithubのpackage.jsonを参照してください。)

※ Firebaseの認証周り全般の解説はこちらを参照してください。

神谷
神谷

Fiewbaseへのログイン関連の処理は盛りだくさんです。

  • メール&パスワードでのログイン
  • メールリンクによるログイン
  • パスワードを忘れたときのメール送信
  • Google IDでのログイン

それぞれ、ある程度独立した処理なので、記事を一気に読むのではなく、必要な機能の部分を拾い読みした方が分かり易いと思います。

  1. ログイン画面
    1. isSignInWithEmailLink:ログイン方法の仕訳処理
  2. メールアドレスとパスワードでログインする処理
    1. handleLogin:ログインのためのハンドラー
    2. loginFirebase.js:ログイン全般モジュール
    3. signInWithEmailAndPassword:メールアドレスとパスワードでログイン
  3. メールリンクでログインする処理
    1. Login.js:メールを送信するためのハンドラー処理
    2. sendEmailLink.js:メールリンク送信処理モジュール
    3. sendSignInLinkToEmail:ログインするためのメールの送信
    4. メールを発送した端末とメールリンクをクリックする端末が同じ場合と異なる場合
      1. メールを発送した端末とメールリンクをクリックする端末が同じ場合
      2. メールを発送した端末とメールリンクをクリックする端末が異なる場合
    5. Login.js:メールリンククリック後の再入力の処理
      1. isSignInWithEmailLink:Login.jsの入力方法の仕訳
    6. loginFirebase.js:メールリンクによるログイン処理
      1. signInWithEmailLink:メールリンクによるログイン処理
  4. パスワードを忘れたときの処理
    1. Login.js:パスワードを忘れたときの処理の抜粋
    2. submitPasswordResetEmail.js:パスワードリセットのためのメール送信処理モジュール
      1. sendPasswordResetEmail:パスワードリセットのためのメール送信
  5. Google IDでログインするときの処理
    1. Login.js:Google IDでログインする処理抜粋
    2. loginFirebase.js:ログイン処理全般モジュール
      1. signInWithPopup:Google IDでログインするためのポップアップ出力処理
  6. 最後に

ログイン画面

Firebaseに認証するだけのアプリのログイン画面
アプリのログイン画面

ログインする方法は、

  • メールアドレスとパスワードでログインする
  • メールリンクでログインする
  • Google IDでログインする

の3種類です。

それに、追加して

「パスワードを忘れたときのパスワード変更」の機能がログイン画面にあります。

ログインの画面を表示するコードは、こちらになります(コードが長いので折りたたんでいます)。

import React from "react";
import { Link, useNavigate } from "react-router-dom";
import { isSignInWithEmailLink } from "firebase/auth";

import { Input, Button } from "@mui/material";
import LoginIcon from "@mui/icons-material/Login";
import EmailIcon from "@mui/icons-material/Email";

import loginFirebase from "../service/firebase/loginFirebase";
import sendEmailLink from "../service/firebase/sendEmailLink";
import { auth, googleProvider } from "../service/firebase/firebase";
import submitPasswordResetEmail from "../service/firebase/submitPasswordResetEmail";
import Title from "./Title";

const Login = () => {
  const navigate = useNavigate();
  let goHome = false;

  if (isSignInWithEmailLink(auth, window.location.href)) {
    let email = window.localStorage.getItem("emailForSignIn");
    if (!email) {
      email = window.prompt("メールアドレスを入力してください");
    }
    loginFirebase("mailLink", email).then((ret) => ret && (goHome = true));
  }
  const handleLogin = (e) => {
    e.preventDefault();
    const [loginEmail, loginPassword] = e.target.elements;
    loginFirebase("email", loginEmail.value, loginPassword.value).then(
      (ret) => ret && navigate("/")
    );
  };

  const handleMailLink = (e) => {
    e.preventDefault();
    const [emailLink] = e.target.elements;
    sendEmailLink(emailLink.value);
  };

  const handlePasswordReset = (e) => {
    e.preventDefault();
    const [resetEmail] = e.target.elements;
    submitPasswordResetEmail(resetEmail.value);
  };

  const handleGoogleLogin = async (e) => {
    loginFirebase("google", googleProvider).then((ret) => ret && navigate("/"));
  };

  return (
    <>
      {goHome ? (
        navigate("/")
      ) : (
        <div>
          <Title />
          <h2>ログイン</h2>
          <div className="login-type">
            <h3>メールでログイン</h3>
            <form onSubmit={handleLogin}>
              <div>
                <label htmlFor="email">メールアドレス: </label>
                <Input
                  id="email"
                  name="email"
                  placeholder="email"
                  type="email"
                />
              </div>
              <div>
                <label htmlFor="password">パスワード: </label>
                <Input
                  type="password"
                  id="password"
                  name="password"
                  placeholder="password"
                />
              </div>
              <div style={{ margin: "5px 0" }}>
                <Button
                  variant="outlined"
                  startIcon={<LoginIcon />}
                  type="submit"
                >
                  ログイン
                </Button>
              </div>
            </form>
            <form onSubmit={handleMailLink}>
              メールリンクによるログインはこちらをクリック
              <div>
                <label htmlFor="emailLink">メールアドレス: </label>
                <Input
                  type="email"
                  id="emailLink"
                  name="emailLink"
                  placeholder="email"
                />
              </div>
              <div style={{ margin: "5px 0" }}>
                <Button
                  variant="outlined"
                  endIcon={<EmailIcon />}
                  type="submit"
                >
                  メール送信
                </Button>
              </div>
            </form>
            <form onSubmit={handlePasswordReset}>
              パスワードを忘れた方はパスワードリセットのメールを送信します。
              <div>
                <label htmlFor="passwordRest">メールアドレス: </label>
                <Input
                  type="email"
                  id="passwordRest"
                  name="passwordRest"
                  placeholder="email"
                />
              </div>
              <div style={{ margin: "5px 0" }}>
                <Button
                    variant="outlined"
                    endIcon={<EmailIcon />}
                    type="submit"
                >メール送信
                </Button>
              </div>
            </form>
            <div>
              ユーザー登録は<Link to={"/signup"}>こちら</Link>から
            </div>
          </div>
          <div className="login-type">
            <h3>Googleでログイン</h3>
            <img
              onClick={handleGoogleLogin}
              src={`${process.env.PUBLIC_URL}/btn_google_signin_light_focus_web.png`}
              alt="Googlでログインのアイコン"
            />
          </div>
        </div>
      )}
    </>
  );
};

export default Login;

isSignInWithEmailLink:ログイン方法の仕訳処理

// Login.js からの抜粋
~ 
 if (isSignInWithEmailLink(auth, window.location.href)) {
    let email = window.localStorage.getItem("emailForSignIn");
    if (!email) {
      email = window.prompt("メールアドレスを入力してください");
    }
    loginFirebase("mailLink", email).then((ret) => ret && (goHome = true));
  }
~

ブラウザのURLの欄に”~/login”が指定されたときには、isSignInWithEmailLink関数がfalseになります。

ログインのためのメールリンクをクリックしたときには、trueになります。

メールリンクによるログイン処理は、『メールリンクでログインする処理』を参照してください。

メールアドレスとパスワードでログインする処理

メールアドレスとパスワードでログインする画面
メールアドレスとパスワードでログインする部分

handleLogin:ログインのためのハンドラー

Login.jsのコードの全体はこちらを参考にしてください。

// Login.js からの抜粋
~
  const handleLogin = (e) => {
    e.preventDefault();
    const [loginEmail, loginPassword] = e.target.elements;
    loginFirebase("email", loginEmail.value, loginPassword.value).then(
      (ret) => ret && navigate("/")
    );
  };
~

メールアドレスとパスワードでログインするボタンをクリックすると、handleLoginに渡ってきます。

そこから、loginFirebase()をコールします。

loginFirebaseの第一引数は、ログインのタイプ(この場合は”email”として、「メールアドレスとパスワードでログインする」という意味です)、emailアドレス、パスワードとなります。

loginFirebaseのリターンはpromisのため、thenでうけます。

thenの中でログインが成功と判定すると、”~/”(ルート、ホーム画面)に移行します。

loginFirebase.js:ログイン全般モジュール

import { signInWithEmailAndPassword, signInWithEmailLink, signInWithPopup } from "firebase/auth";
import { auth } from "./firebase";
import { issueMsg } from "../common/issueMsg";

const loginFirebase = async (type,loginProvider, password = null) => {
  if (type === 'email' ){ // ここからメールアドレスとパスワードでログインする処理
    try {
      await signInWithEmailAndPassword(
        auth,
        loginProvider,
        password
      );
      return true;
    } catch (error) {
      if (error.code === 'auth/invalid-email') {
        issueMsg('正しいメールアドレスを入力してください');
      } else if (error.code === 'auth/user-not-found') {
        issueMsg('当該メールアドレスは、ユーザー登録がされていません。');
      } else if (error.code === 'auth/wrong-password') {
        issueMsg('パスワードが不正です');
      } else if (error.code === 'auth/too-many-requests') {
        issueMsg('パスワードの入力不正が規定回数を超えました。');
      } else if (error.code === 'auth/user-disabled') {
        issueMsg('ユーザが無効になっています。');
      } else {
        issueMsg('ログインで不正が発生しました。',error.code);
      }
      console.error(error);
      return false;
    }
  } else if(type === 'mailLink') {
    try {
      await signInWithEmailLink(auth, loginProvider, window.location.href);
    } catch(error) {
      issueMsg('メール認証ログインに失敗しました',error.code)
    }
  } else if(type === 'google') {
    try{
      await signInWithPopup(auth, loginProvider);
    } catch(error) {
      if (error.code === 'auth/popup-closed-by-user') {
        issueMsg('GoogleIdでのログインがキャンセルされました。');
      } else {
        console.log(error.code);
        alert(error.message);
      }
    }
  }
}

export default loginFirebase;

入力の第一引数が”email”の時に、メールリンクでログインする処理になります。

signInWithEmailAndPassword:メールアドレスとパスワードでログイン

// loginFirebase.js からの抜粋
~ 
 await signInWithEmailAndPassword(
    auth,
    loginProvider,
    password
  );
~

signInWithEmailAndPasswordがFirebaseのアプリにログインする関数。

authと、loginProvider(メールアドレス)、パスワードが引数です。

authについてはこちらを参照してください。

メールリンクでログインする画面
メールリンクでログインする処理
神谷
神谷

パスワードを忘れてしまったけど、アプリに登録したメールは使用できる状態の時に使う機能です。

Login.js:メールを送信するためのハンドラー処理

Login.jsのコードの全体はこちらを参考にしてください。

// Login.js からの抜粋
~ 
 const handleMailLink = (e) => {
    e.preventDefault();
    const [emailLink] = e.target.elements;
    sendEmailLink(emailLink.value);
  };
~

「メールリンクによるログインはこちらをクリック」にユーザー登録してあるメールアドレスを入力して、「メール送信」ボタンをクリックすると、handleMailLinkに渡ってきます。

入力のメールアドレスを使用して、sendEmailLink()をコールします。

sendEmailLink.js:メールリンク送信処理モジュール

import { sendSignInLinkToEmail } from "firebase/auth";
// import { auth } from "./firebase";
import { issueMsg } from "../common/issueMsg";
import { auth } from "./firebase";

const sendEmailLink = async (email) => {

  const REDIRECT_URL_LOGIN = process.env.REACT_APP_REDIRECT_URL_LOGIN;

  const actionCodeSettings = {
    url: REDIRECT_URL_LOGIN,
    handleCodeInApp: true,
  }

  try {
    await sendSignInLinkToEmail(auth,email,actionCodeSettings);
    window.localStorage.setItem("emailForSignIn", email);
    issueMsg('メール認証のためのメール送信しました。');

  } catch(error) {
    console.log(error);
    issueMsg('メール認証のためのメール送信に失敗しました。',error.code);
  }
}

export default sendEmailLink;

sendSignInLinkToEmail:ログインするためのメールの送信

sendSignInLinkToEmail()がメールリンクでログインするURLを貼ったメールを送信するためのFirebaseの関数。

authと送信するemailアドレスと、actionCodeSettingsを設定します。

actionCodeSettingsには、メールのリンクをクリックしたときに表示するURLを設定します。

神谷
神谷

handleCodeInAppはtrueにしています。Fiewbaseのマニュアルではよくわからなかったのですが、trueにしたおいたら、上手くいって結果オーライという状態です。

あるサイトでは、「モバイルでメールを受けた場合にはtrueにする」とのことでしたが、検証はしていません。

sendSignInLinkToEmail()をコールすることによって、指定したメールアドレスにログインするためのリンクがメールされます。

メールリンクによるログインをするためのメールの内容
sendSignInLinkToEmailで送信したメールの内容

そのリンクをクリックすることによって、ログインができます。

神谷
神谷

メールの文面はFirebaseの定型のもので、自分ではカスタマイズできません。言語を選択することはできます。

「ADMTodo」の所はFirebaseのプロジェクトを作成した時に自動的に決まります。

カスタマイズしたい方は、

Firebase Consoleのプロジェクトの画面から、「プロジェクトの概要」-歯車マーク-「プロジェクトの設定」-「プロジェクト」-「公開設定」のペンマークから変更できます。

Firebaseのプロジェクトの公開名を変更する。

メールを発送した端末とメールリンクをクリックする端末が同じ場合と異なる場合

メールを発送した端末とメールリンクをクリックする端末が同じ場合と異なる場合で処理が異なってきます。

メールを発送した端末とメールリンクをクリックする端末が同じ場合

メールを発送した端末とメールリンクをクリックする端末が同じ場合は、sendSignInLinkToEmailを発行した時点で、ログインに必要な情報を端末に確保します。

その情報を元にログインが可能なので、メールリンクをクリックした時点で、Firebaseのアプリにログインできます。

メールを発送した端末とメールリンクをクリックする端末が異なる場合

メールを発送した端末とメールリンクをクリックする端末が異なる場合は、ログインに必要な情報が端末に存在しないため、

  • ログインメールアドレスの再入力が必要
  • メールリンクをクリックした端末からのsignInWithEmailLink発行によるサインイン処理

が必要になります。

Login.js:メールリンククリック後の再入力の処理

Login.jsのコードの全体はこちらを参考にしてください。

// Login.js 抜粋
 
~
 if (isSignInWithEmailLink(auth, window.location.href)) {
    let email = window.localStorage.getItem("emailForSignIn");
    if (!email) {
      email = window.prompt("メールアドレスを入力してください");
    }
    loginFirebase("mailLink", email).then((ret) => ret && (goHome = true));
  }
~

isSignInWithEmailLink:Login.jsの入力方法の仕訳

メールのURLをクリックすると”~/login”に渡ってきます。

直接”~/login”をURLに指定してきた場合と、メールリンクによるものかどうかを判断するのが、isSignInWithEmailLink(auth, window.location.href)。

メールリンクで渡ってきた、なおかつメールを送信した端末とメールリンクをクリックした端末が異なる場合(分かりにくい場合はこちらを参照。)には、loginFirebase()の第一引数にmailLink”を指定しコールして、直接ログイン処理を行います。

let email = window.localStorage.getItem(“emailForSignIn”)でemailを求めているのは、メールリンクをクリックした端末に、email情報がセットしてあるかどうかを確認しています。

email情報が無ければ、ユーザにemail情報の入力を求めます。

メールリンクによるログイン要求と、実際にログインする端末が異なるときなどに発生します。

メールリンクによるログインが成功すると、goHome=trueになり、Home画面を出力します。

神谷
神谷

「メールリンクによるログイン要求と、実際にログインする端末が異なる」というテストは、loalhostではテストは難しいので、レンタルサーバーにデプロイした後に、テストをする必要がありますね。

loalhostでテストするときには、ローカルストレージを細工するという方法はあるとは思いますが。私はやっていません。

loginFirebase.js:メールリンクによるログイン処理

// loginFirebase.js抜粋

~
 else if(type === 'mailLink') {
    try {
      await signInWithEmailLink(auth, loginProvider, window.location.href);
    } catch(error) {
      issueMsg('メール認証ログインに失敗しました',error.code)
    }
~

signInWithEmailLink:メールリンクによるログイン処理

loginFirebaseの第一引数に’mailLink’が指定されている場合は、signInWithEmailLinkを発行して、Firebaseのアプリにログインを行います。

パスワードを忘れたときの処理

パスワードを忘れたときパスワード再設定のメールを送信する画面

Login.js:パスワードを忘れたときの処理の抜粋

Login.jsのコードの全体はこちらを参考にしてください。

// Login.jsから抜粋 
~
 const handlePasswordReset = (e) => {
    e.preventDefault();
    const [resetEmail] = e.target.elements;
    submitPasswordResetEmail(resetEmail.value);
  };
~

「パスワードを忘れた方はパスワードリセットのメールを送信します。」にユーザー登録してあるメールアドレスを入力して、「メール送信」ボタンをクリックすると、handlePasswordRestに渡ってきます。

入力のメールアドレスを使用して、submitPasswordResetEmail()をコールします。

submitPasswordResetEmail.js:パスワードリセットのためのメール送信処理モジュール

// submitPasswordResetEmail.js

import { sendPasswordResetEmail } from "firebase/auth";
import { issueMsg } from "../common/issueMsg";
import { auth } from "./firebase";


const submitPasswordResetEmail = async (email) => {
  const actionCodeSettings = {
    // パスワード再設定後のリダイレクト URL
    url: process.env.REACT_APP_REDIRECT_URL_LOGIN,
    handleCodeInApp: false,
  }
  try{
    await sendPasswordResetEmail(auth,email,actionCodeSettings);
    issueMsg(`${email}にパスワードリセットのためのメールを送信しました。`);
  } catch(error){
    console.log(error.code);
    console.log(error);
    issueMsg('パスワードリセットのメール送信に失敗しました。', error.code);
  }
}

export default submitPasswordResetEmail;

sendPasswordResetEmail:パスワードリセットのためのメール送信

Firebaseの関数のsendPasswordResetEmail()をコールします。

actionCodeSettingsのurl:には、パスワードをリセットした時のURLをセット。

リセット用のパスワードが設定されると、ログイン画面に移行するということです。

神谷
神谷

「handleCodeInApp: false」については、今一つはっきりしません。サンプルにしたコードと同じ設定にしました。Web,andoroid,iosのカラミだと思いますが、後で確認しておこうと思います。

sendPasswordResetEmail()をコールすることよって、入力したemailにパスワードリセットするためのメールが送信されます。

パスワードリセットのメールの内容
パスワードリセットの為のメール内容

このメールのリンクをクリックすると、

パスワードリセットの画面
パスワードリセット画面

パスワードリセットのための画面が出力し、入力して「保存」をクリックするとパスワードが変更されます。

神谷
神谷

パスワードリセットの画面はカスタマイズができます。私は、カスタマイズをしない方法で作成しました。

Google IDでログインするときの処理

Google IDでログインするための画面
Google IDでログインする画面
神谷
神谷

Googleでログインするときなどのボタンは、Googleから提供されています。

必ずこれを使わなければならないということではないけど、強く推奨するとのことでした。

詳細はこちらから

Login.js:Google IDでログインする処理抜粋

Login.jsのコードの全体はこちらを参考にしてください。

// Login.jsから抜粋

~
  const handleGoogleLogin = async (e) => {
    loginFirebase("google", googleProvider).then((ret) => ret && navigate("/"));
  };
~

「Googleでログイン」の「Sign in with Google」ボタンをクリックすると、handleGoogleLoginに渡ってきます。

第一引数に”google”を指定して、第二引数に”googleProvider”を指定してloginFirebase()をコールします。

googleProviderについては、firebase.js(Firebase関連の初期処理)で、求めています。

Google IDでログインするときに必要となります。

firebase.jsに関してはこちらを参考にしてください。

loginFirebase.js:ログイン処理全般モジュール

// loginFirebase.js

import { signInWithEmailAndPassword, signInWithEmailLink, signInWithPopup } from "firebase/auth";
import { auth } from "./firebase";
import { issueMsg } from "../common/issueMsg";

const loginFirebase = async (type,loginProvider, password = null) => {
  if (type === 'email' ){
    try {
      await signInWithEmailAndPassword(
        auth,
        loginProvider,
        password
      );
      return true;
    } catch (error) {
      if (error.code === 'auth/invalid-email') {
        issueMsg('正しいメールアドレスを入力してください');
      } else if (error.code === 'auth/user-not-found') {
        issueMsg('当該メールアドレスは、ユーザー登録がされていません。');
      } else if (error.code === 'auth/wrong-password') {
        issueMsg('パスワードが不正です');
      } else if (error.code === 'auth/too-many-requests') {
        issueMsg('パスワードの入力不正が規定回数を超えました。');
      } else if (error.code === 'auth/user-disabled') {
        issueMsg('ユーザが無効になっています。');
      } else {
        issueMsg('ログインで不正が発生しました。',error.code);
      }
      console.error(error);
      return false;
    }
  } else if(type === 'mailLink') {
    try {
      await signInWithEmailLink(auth, loginProvider, window.location.href);
    } catch(error) {
      issueMsg('メール認証ログインに失敗しました',error.code)
    }
  } else if(type === 'google') { // ここがGoogle IDでログインする処理
    try{
      await signInWithPopup(auth, loginProvider);
    } catch(error) {
      if (error.code === 'auth/popup-closed-by-user') {
        issueMsg('GoogleIdでのログインがキャンセルされました。');
      } else {
        console.log(error.code);
        alert(error.message);
      }
    }
  }
}

export default loginFirebase;

signInWithPopup:Google IDでログインするためのポップアップ出力処理

loginFirebaseの引数に”google”が指定されていた時には、signInWithPopupをコールします。

そうすると、現在端末にログインしているGoogle IDの一覧のポップアップウインドウが出力されるので、

Google IDでログインするときのポップアップ画面
Google IDでログインするためのポップアップ画面

ログインするIDをクリックしてログインします。

Google IDでログインする場合には、アプリケーションへの登録とログインが同時に行われます。

神谷
神谷

Google IDを使ってのFirebaseへのログイン処理はとても簡単。Firebaseを使ったアプリを作成する、教材でよく取り扱われています。

ただ、Google IDをアプリから削除する(退会処理)は結構難しいです。

最後に

以下が、当該Reactのプロジェクトのsrc配下のファイル構造です。

SRC
│  App.css
│  App.js
│  App.test.js
│  index.css
│  index.js
│  reportWebVitals.js
│  service-worker.js
│  serviceWorkerRegistration.js
│  setupTests.js
│
├─components
│  │  Home.js
│  │  HomeChild.js
│  │  Login.js
│  │  PrivateRoute.js
│  │  PublicRoute.js
│  │  SignUp.js
│  │  Title.js
│  │
│  └─styles
│          portal.css
│
├─context
│      AuthContext.js
│
└─service
    ├─authenticationProcess
    │      changePassword.js
    │      delUser.js
    │
    ├─common
    │      issueMsg.js
    │
    └─firebase
            firebase.js
            getCredential.js
            loginFirebase.js
            registration.js
            sendEmailLink.js
            submitPasswordResetEmail.js

この記事では、

  • Login.js
  • loginFirebase.js
  • sendEmailLink.js
  • submitPasswordResetEmail.js

の解説をしました。

このブログに掲載されているコードは、著作権者(私)に帰属します。コードを自由に使用、複製、改変、配布、販売、サブライセンスできます。また、コードを使用した結果、何らかの損害が発生した場合でも、著作権者は一切責任を負いません。

タイトルとURLをコピーしました