Chapter 09

Auth:メールアドレスの有効化

masalib
masalib
2020.12.24に更新

メールアドレスが本当に存在しているのかは実際にメールを送らないとわかりません。Firebaseではメールを送る事でメールアドレスを有効化する事ができます。

メールアドレスの有効化とは

  1. userに認証URLがついたメールを送付
  2. userが認証URLにアクセス

この2つで有効化にできます

ちなみにメール有効化はユーザーがログインしていないと使えない機能です。
擬似的にアドレスを作ることはできますがこの本ではおこないません。

メールのテンプレートの変更

パスワード初期化と同様にメールのテンプレートはFirebase側が用意してくれています。ただデフォルト設定は英語なのでもし変更していない人は日本語にします。

  1. Firebaseにログインして該当のプロジェクトを選択する。
  2. メニューのAuthenticationのTemplatesを選択する
  3. テンプレート言語設定を英語から日本語に変える

※補足

  • このテンプレートの文章はスパムメールの対策で修正する事ができません

メールアドレス有効化の処理

パスワードを忘れた処理と同様にcontextに処理をつくってその関数を共有させます

プログラム全文(長文なので注意)
/src/contexts/AuthContext.js
import React, { useContext, useState, useEffect } from "react";
import { auth } from "../firebase";

const AuthContext = React.createContext();

export function useAuth() {
  return useContext(AuthContext);
}

export function AuthProvider({ children }) {
  const [currentUser, setCurrentUser] = useState();
  const [loading, setLoading] = useState(true);

  function signup(email, password) {
    return auth.createUserWithEmailAndPassword(email, password);
  }
  function login(email, password) {
    return auth.signInWithEmailAndPassword(email, password);
  }
  function logout() {
    return auth.signOut();
  }

  function resetPassword(email) {
    // .env use case      url: process.env.REACT_APP_MAIL_URL + '?email=' + email,
    // local dev case     url: "http://localhost:3000/?email=" + email,
    // product case     url: "https://you-domain/?email=' + email,
    const actionCodeSettings = {
      url: "https://sszxu.csb.app/"
    };
    //return auth.sendPasswordResetEmail(email, actionCodeSettings);
    return auth.sendPasswordResetEmail(email, actionCodeSettings);
  }

+   function sendEmailVerification() {
+     // .env use case      url: process.env.REACT_APP_MAIL_URL + 'dashboard'
+     // local dev case     url: "http://localhost:3000/dashboard"
+     // product case     url: "https://you-domain/dashboard'
+     const actionCodeSettings = {
+       url: "https://sszxu.csb.app/dashboard"
+     };
+     return currentUser.sendEmailVerification(actionCodeSettings);
+   }

  const value = {
    currentUser,
    signup,
    login,
    logout,
    resetPassword,
+     sendEmailVerification
  };

  useEffect(() => {
    // Firebase Authのメソッド。ログイン状態が変化すると呼び出される
    auth.onAuthStateChanged((user) => {
      setCurrentUser(user);
      setLoading(false);
    });
  }, []);

  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  );
}

修正の内容でドメインが「xxx.xxx.app」になっていますが環境変数でおこなうのが正しいです。環境にあわせて修正してください

      const actionCodeSettings = {
        url: process.env.REACT_APP_MAIL_URL + 'dashboard' ,
      }

ダッシュボードにボタンを追加

最終的にはユーザーのプロフィールの画面などで有効化のボタンを配置したいのですが、手っ取り早くダッシュボードに設置しました

/src/components/Dashboard.js
import React, { useState } from "react";
import { Link, useHistory } from "react-router-dom";
import Button from "@material-ui/core/Button";
import { useAuth } from "../contexts/AuthContext";

const Dashboard = () => {
+   const { currentUser, logout, sendEmailVerification } = useAuth();
  const history = useHistory();
  const [error, setError] = useState("");

  async function handleLogout() {
    setError("");

    try {
      await logout();
      history.push("/");
    } catch {
      setError("Failed to log out");
    }
  }

+   async function handlesendEmailVerification() {
+     setError("");
+     try {
+       await sendEmailVerification();
+       setError("メールをおくりました。メール有効化をお願いします");
+     } catch (e) {
+       console.log(e);
+       setError("有効化メールの送信に失敗しました");
+     }
+   }

  return (
    <div>
      Dashboard:
      {error && <div style={{ color: "red" }}>{error}</div>}
      <div>
        <strong>Email:</strong> {currentUser.email}
      </div>
      <div>
        <strong>ハンドル名:</strong> {currentUser.displayName}
      </div>
      <h2>
        <Link to="/login">Login</Link>
      </h2>
      <h2>
        <Link to="/signup">signup</Link>
      </h2>
      <Button color="primary" onClick={handleLogout}>
        Logout
      </Button>
+       {!currentUser.emailVerified && (
+         <div>
+           メールアドレスが有効化されていません{" "}
+           <Button color="primary" onClick={handlesendEmailVerification}>
+             メールアドレス有効化
+           </Button>
+         </div>
+       )}
    </div>
  );
};
export default Dashboard;

補足

  • currentUserはログインしているユーザーの情報などが入っているオブジェクトです。
  • currentUser.emailVerifiedはログインしているユーザーがメールアドレスが有効化されているのかを判別する関数です(true:有効、false:無効)
  • 有効化している人に有効化のボタンは必要ないので切り替えています

結果

  1. handlesendEmailVerificationのボタンを押す

  2. メールが届く

  3. URLにアクセスして完了画面が表示される

  4. サイトのトップに戻ってくる
    メール有効化のボタンが消えます、currentUserの内容を確認すると

    emailVerifiedという設定値が変わっていることが確認できます

補足

メール有効化処理をユーザー作成時におこないたい人は以下の記事を参考にしてください

https://stackoverflow.com/questions/42872594/how-to-send-email-verification-after-user-creation-with-firebase-cloud-functions

メール有効化していない状態を会員として認識させたくない場合はログイン認証を修正する形になります。(本来はプロバイダーを組むべきなのですが、そこは省略しています。)

/src/components/AuthFirebaseRoute.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";

export default function AuthFirebaseRoute({ component: Component, ...rest }) {
  const { currentUser } = useAuth();

  return (
    <Route
      {...rest}
      render={(props) => {
-         return currentUser ? <Component {...props} /> : <Redirect to="/login" />
+         if (currentUser) {
+           if (currentUser.emailVerified) {
+             return <Component {...props} />;
+           } else {
+             return <Redirect to="/noemailverified" />;
+           }
+         } else {
+           return <Redirect to="/login" />;
+         }
      }}
    ></Route>
  );
}

メールアドレスの有効化されていない状態のページを作成します

/src/components/NoEmailVerified.js
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import Button from "@material-ui/core/Button";
import { useAuth } from "../contexts/AuthContext";

const NoEmailVerified = () => {
  const { currentUser, logout, sendEmailVerification } = useAuth();
  const history = useHistory();
  const [error, setError] = useState("");

  if (!currentUser) {
    //ログインしていない状態なのでリダイレクトする(本来、ここにはこない)
    history.push("/");
  }

  async function handleLogout() {
    setError("");

    try {
      await logout();
      history.push("/");
    } catch {
      setError("Failed to log out");
    }
  }

  async function handlesendEmailVerification() {
    setError("");
    try {
      await sendEmailVerification();
      setError("メールをおくりました。メール有効化をお願いします");
    } catch (e) {
      console.log(e);
      setError("有効化メールの送信に失敗しました");
    }
  }

  return (
    <div>
      {error && <div style={{ color: "red" }}>{error}</div>}
      {currentUser && (
        <>
          {!currentUser.emailVerified && (
            <div>
              メールアドレスが有効化されていません <br />
              <Button color="primary" onClick={handlesendEmailVerification}>
                メールアドレス有効化
              </Button>
            </div>
          )}
        </>
      )}
      <Button color="primary" onClick={handleLogout}>
        Logout
      </Button>
    </div>
  );
};
export default NoEmailVerified;

ルーティングを追加します。

/src/components/App.js
import React from "react";
import Signup from "./Signup";
import Home from "./Home";
import Login from "./Login";
import Dashboard from "./Dashboard";
+ import NoEmailVerified from "./NoEmailVerified";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { AuthProvider } from "../contexts/AuthContext";
import AuthFirebaseRoute from "./AuthFirebaseRoute"; //ログイン認証
import ForgotPassword from "./ForgotPassword";

export default function App() {
  return (
    <Router>
      <AuthProvider>
        <Switch>
          <Route path="/signup" component={Signup} />
          <Route path="/forgotPassword" component={ForgotPassword} />
          <Route exact path="/" component={Home} />

          <Route path="/login" component={Login} />
+           <Route path="/noemailverified" component={NoEmailVerified} />

          <AuthFirebaseRoute path="/dashboard" component={Dashboard} />
        </Switch>
      </AuthProvider>
    </Router>
  );
}

結果の画面

有効化しないと会員として「認識する」、「しない」かはサイトのセキュリティーポリシーの部分になるので実際に組む場合は、プロジェクトリーダーなどに確認してください。この処理の欠点としては次に記載するメールアドレスの変更をするといきなり会員として見なされないという状況になるです。

終了時点のソース

  • メールアドレス有効化しないとダッシュボードに入れないバージョンです。もし入れるパターンにしたい場合はログイン認証を変更してください。