メールリンクを使用して Firebase 認証を行う

Firebase Authentication を使用すると、ログイン用のリンクを含むメールをユーザーに送信し、ログインしてもらうことができます。このプロセスでは、ユーザーのメールアドレスの検証も行います。

メールでのログインには、次のような利点があります。

  • 登録とログインが簡単になります。
  • アプリ間でパスワードが再利用されるリスクが低くなります。パスワードを再利用すると、適切なパスワードを選択していても、セキュリティが低下するおそれがあります。
  • ユーザー認証で、ユーザーがメールアドレスの正当な所有者であることも確認できます。
  • アクセス可能なメール アカウントがあればそれだけでログインできます。電話番号やソーシャル メディアのアカウントは必要ありません。
  • パスワードを入力(あるいは記憶)しなくても、安全にログインできます。モバイル デバイスでは、パスワードの入力や記憶が面倒な場合があります。
  • 前にメール ID(パスワードや認証連携)でログインしたユーザーをメールのみによるログインにアップグレードできます。たとえば、パスワードを忘れてしまった場合、パスワードを再設定しなくてもログインできます。

始める前に

  1. まだ行っていない場合は、スタートガイドの手順に沿って操作してください。

  2. Firebase プロジェクトでメールリンク ログインを有効にします。

    メールリンクでユーザーをログインさせるには、Firebase プロジェクトでメール プロバイダとメールリンクのログイン方法を有効にする必要があります。

    1. Firebase コンソールで [Authentication] セクションを開きます。
    2. [Sign-in method] タブで [メール / パスワード] を有効にします。メールリンク ログインを使用するには、メール / パスワードによるログインを有効にする必要があります。
    3. 同じセクションで、ログイン方法として [メールリンク(パスワードなしでログイン)] を有効にします。
    4. [保存] をクリックします。

認証フローを開始するには、ユーザーにメールアドレスの入力を求めるインターフェースを表示します。次に、sendSignInLinkToEmail() を呼び出し、ユーザーのメールアドレスに認証リンクを送信するように Firebase にリクエストします。

  1. メールリンクの作成方法を Firebase に伝える ActionCodeSettings オブジェクトを作成します。次のフィールドを設定します。

    • url: メールに埋め込むディープリンク。状態も一緒に渡します。リンクのドメインは、Firebase コンソールで承認済みドメインの許可リストに登録する必要があります。これは、[Sign-in method] タブ([Authentication] > [Sign-in method])で行えます。アプリがユーザーのデバイスにインストールされていない場合や、アプリをインストールできない場合、この URL にユーザーをリダイレクトします。

    • androidPackageNameIOSBundleId: Android または iOS デバイスでログインリンクを開くアプリ。モバイルアプリ経由でメールのアクション リンクを開く方法については、Firebase Dynamic Links の構成をご覧ください。

    • handleCodeInApp: true に設定します。他の帯域外メール アクション(パスワードのリセットやメールアドレスの確認など)と異なり、ログインはアプリ内で完結させる必要があります。これは、フローの最後でユーザーがログインに成功し、認証状態がアプリ内で維持される必要があるためです。

    • dynamicLinkDomain: 1 つのプロジェクトに複数のカスタム ダイナミック リンク ドメインが定義されている場合、指定のモバイルアプリ経由でリンクを開く際に使用するドメインを指定します(example.page.link など)。指定しない場合、最初のドメインが自動的に選択されます。

    var acs = ActionCodeSettings(
        // URL you want to redirect back to. The domain (www.example.com) for this
        // URL must be whitelisted in the Firebase Console.
        url: 'https://www.example.com/finishSignUp?cartId=1234',
        // This must be true
        handleCodeInApp: true,
        iOSBundleId: 'com.example.ios',
        androidPackageName: 'com.example.android',
        // installIfNotAvailable
        androidInstallApp: true,
        // minimumVersion
        androidMinimumVersion: '12');
    
  2. ユーザーにメールアドレスを求めます。

  3. ユーザーのメールアドレスに認証リンクを送信し、ユーザーが同じデバイスでメールによるログインを完了する場合に備えてこのメールアドレスを保存します。

    var emailAuth = 'someemail@domain.com';
    FirebaseAuth.instance.sendSignInLinkToEmail(
            email: emailAuth, actionCodeSettings: acs)
        .catchError((onError) => print('Error sending email verification $onError'))
        .then((value) => print('Successfully sent email verification'));
    });
    

セキュリティに関する懸念

意図しないユーザーやデバイスがログインリンクでログインしないように、Firebase Auth はログインフローの完了時にユーザーにメールアドレスの入力を求めます。入力されたメールアドレスがログインリンクの送信アドレスと一致しないと、ログインに失敗します。

ログインメールの送信時に、SharedPreferences などを使用してメールアドレスをローカルに保存しておくことで、リンクをリクエストした同じデバイスでログインリンクを開くユーザーの認証フローが簡単になります。メールアドレスが一致した場合、このアドレスを使用してフローを完了します。セッション インジェクションの危険性があるため、ユーザーのメールアドレスをリダイレクト URL パラメータに渡して再利用しないでください。

ログインが完了すると、未確認のログインはすべて破棄され、既存のセッションが無効になります。たとえば、未確認のアカウントが同じメールとパスワードですでに作成されている場合、所有者を装って未確認アカウントを作成した人物が未確認のメールアドレスとパスワードでログインできないように、ユーザーのパスワードが削除されます。

中間のサーバーでリンクがインターセプトされないように、本番環境では HTTPS URL を使用してください。

Firebase Authentication は、Firebase Dynamic Links を使用してモバイル デバイスにメールリンクを送信します。モバイルアプリ経由でログインを行う場合、リンクの受信を検出し、ディープリンクを解析してログインを完了するようにアプリを構成する必要があります。

  1. ガイドの手順に沿って、Flutter で Dynamic Links を受信するようにアプリを設定します。

  2. リンクハンドラで、リンクがメールリンク認証用かどうか確認します。メールリンク認証用であれば、ログイン プロセスを完了します。

    // Confirm the link is a sign-in with email link.
    if (FirebaseAuth.instance.isSignInWithEmailLink(emailLink)) {
      try {
        // The client SDK will parse the code from the link for you.
        final userCredential = await FirebaseAuth.instance
            .signInWithEmailLink(email: emailAuth, emailLink: emailLink);
    
        // You can access the new user via userCredential.user.
        final emailAddress = userCredential.user?.email;
    
        print('Successfully signed in with email link!');
      } catch (error) {
        print('Error signing in with email link.');
      }
    }
    

この認証方法は既存ユーザーの認証でも利用できます。たとえば、ユーザーが別のプロバイダにおいて電話番号などで認証されている場合、既存のアカウントにメールリンクによる認証方法を追加できます。

オペレーションの後半部分が異なります。

final authCredential = EmailAuthProvider
    .credentialWithLink(email: emailAuth, emailLink: emailLink.toString());
try {
    await FirebaseAuth.instance.currentUser
        ?.linkWithCredential(authCredential);
} catch (error) {
    print("Error linking emailLink credential.");
}

これは、重要なオペレーションの前にメールリンク ユーザーを再認証する場合にも使用できます。

final authCredential = EmailAuthProvider
    .credentialWithLink(email: emailAuth, emailLink: emailLink.toString());
try {
    await FirebaseAuth.instance.currentUser
        ?.reauthenticateWithCredential(authCredential);
} catch (error) {
    print("Error reauthenticating credential.");
}

ただし、元のユーザーがログインしていない別のデバイスでフローを完了しようとすると、フローが完了できない場合があります。この場合、ユーザーにエラーを表示し、強制的に同じデバイスでリンクを開かせます。オペレーションの種類やユーザーの UID の情報を提供するため、リンクと一緒に状態を渡すこともできます。

メールのパスワードとリンクベースの両方のログイン方法をサポートする場合、fetchSignInMethodsForEmail を使用して、それぞれのログイン方法を区別します。この方法は、ユーザーにメールアドレスの入力を求め、入力後にログイン方法を提示する ID 優先のフローで役立ちます。

try {
    final signInMethods =
        await FirebaseAuth.instance.fetchSignInMethodsForEmail(emailAuth);
    final userExists = signInMethods.isNotEmpty;
    final canSignInWithLink = signInMethods
        .contains(EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD);
    final canSignInWithPassword = signInMethods
        .contains(EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD);
} on FirebaseAuthException catch (exception) {
    switch (exception.code) {
        case "invalid-email":
            print("Not a valid email address.");
            break;
        default:
            print("Unknown error.");
    }
}

上述のように、メール / パスワードとメールリンクは、異なるログイン方法を使用する同じ EmailAuthProvider(同じ PROVIDER_ID)と考えることができます。

次のステップ

ユーザーが作成した新しいアカウントは Firebase プロジェクトの一部として保存されます。このアカウントは、ユーザーが使用するログイン方法に関係なく、プロジェクトのすべてのアプリでユーザーの識別に使用できます。

アプリでは、User オブジェクトからユーザーの基本的なプロフィール情報を取得します。ユーザーを管理するをご覧ください。

Firebase Realtime Database と Cloud Storage のセキュリティ ルールでは、ログイン済みユーザーの一意のユーザー ID を auth 変数から取得し、それを使用して、ユーザーがアクセス可能なデータを制御できます。

既存のユーザー アカウントに認証プロバイダの認証情報をリンクすることで、ユーザーは複数の認証プロバイダを使用してアプリにログインできるようになります。

ユーザーのログアウトを行うには、signOut() を呼び出します。

await FirebaseAuth.instance.signOut();