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

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

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

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

準備

初期化スニペットを Firebase コンソールからプロジェクトにコピーしていない場合は、Firebase を JavaScript プロジェクトに追加するの手順に沿って初期化スニペットをコピーします。

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

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

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

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

    • url: メールに埋め込むディープリンク。状態も一緒に渡します。リンクのドメインは、Firebase コンソールで承認済みドメインのホワイトリストに登録する必要があります。これは、[ログイン方法] タブ([Authentication] > [ログイン方法])で行えます。
    • androidios: Android または iOS 端末でログインリンクを開くアプリ。モバイルアプリ経由でメールのアクション リンクを開く方法については、Firebase Dynamic Links の構成をご覧ください。
    • handleCodeInApp: true に設定します。パスワードのリセットやメールアドレスの確認など、他の帯域外メール アクションと異なり、ログインはアプリ内で完結させる必要があります。これは、フローの最後でユーザーがログインに成功し、認証状態がアプリ内で維持される必要があるためです。
    
    var 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,
      iOS: {
        bundleId: 'com.example.ios'
      },
      android: {
        packageName: 'com.example.android',
        installApp: true,
        minimumVersion: '12'
      }
    };
    

    ActionCodeSettings の詳細については、メール アクションで状態を渡すをご覧ください。

  2. ユーザーにメールアドレスを求めます。

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

    
    firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings)
      .then(function() {
        // The link was successfully sent. Inform the user.
        // Save the email locally so you don't need to ask the user for it again
        // if they open the link on the same device.
        window.localStorage.setItem('emailForSignIn', email);
      })
      .catch(function(error) {
        // Some error occurred, you can inspect the code: error.code
      });
    

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

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

ログインメールの送信時に、localStorage や Cookie などを使用してメールアドレスをローカルに保存しておくことで、リンクをリクエストした同じ端末でログインリンクを開くユーザーの認証フローが簡単になります。メールアドレスが一致した場合、このアドレスを使用してフローを完了します。セッション インジェクションが可能になるため、ユーザーのメールアドレスをリダイレクト URL パラメータに渡して再利用してはなりません。

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

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

ウェブページでログインを完了する

メールリンクで使用するディープリンクの形式は、帯域外メール アクションで使用される形式(メールアドレスの確認、パスワードのリセット、メールアドレスの変更取り消しなどで使用される形式)と同じです。Firebase Auth では、isSignInWithEmailLink API でリンクがメールリンクのログインかどうかを確認します。

ランディング ページでログインを完了するには、signInWithEmailLink を呼び出し、ワンタイム コードを含む実際のメールリンクとユーザーのメールアドレスを渡します。


// Confirm the link is a sign-in with email link.
if (firebase.auth().isSignInWithEmailLink(window.location.href)) {
  // Additional state parameters can also be passed via URL.
  // This can be used to continue the user's intended action before triggering
  // the sign-in operation.
  // Get the email if available. This should be available if the user completes
  // the flow on the same device where they started it.
  var email = window.localStorage.getItem('emailForSignIn');
  if (!email) {
    // User opened the link on a different device. To prevent session fixation
    // attacks, ask the user to provide the associated email again. For example:
    email = window.prompt('Please provide your email for confirmation');
  }
  // The client SDK will parse the code from the link for you.
  firebase.auth().signInWithEmailLink(email, window.location.href)
    .then(function(result) {
      // Clear email from storage.
      window.localStorage.removeItem('emailForSignIn');
      // You can access the new user via result.user
      // Additional user info profile not available via:
      // result.additionalUserInfo.profile == null
      // You can check if the user is new or existing:
      // result.additionalUserInfo.isNewUser
    })
    .catch(function(error) {
      // Some error occurred, you can inspect the code: error.code
      // Common errors could be invalid email and invalid or expired OTPs.
    });
}

モバイルアプリでログインを完了する

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

Android アプリでメールリンクによるログインを処理する方法については、Android のガイドをご覧ください。

iOS アプリでメールリンクによるログインを処理する方法については、iOS のガイドをご覧ください。

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

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


// Construct the email link credential from the current URL.
var credential = firebase.auth.EmailAuthProvider.credentialWithLink(
    email, window.location.href);

// Link the credential to the current user.
firebase.auth().currentUser.linkAndRetrieveDataWithCredential(credential)
  .then(function(usercred) {
    // The provider is now successfully linked.
    // The phone user can now sign in with their phone number or email.
  })
  .catch(function(error) {
    // Some error occurred.
  });

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


// Construct the email link credential from the current URL.
var credential = firebase.auth.EmailAuthProvider.credentialWithLink(
    email, window.location.href);

// Re-authenticate the user with this credential.
firebase.auth().currentUser.reauthenticateAndRetrieveDataWithCredential(credential)
  .then(function(usercred) {
    // The user is now successfully re-authenticated and can execute sensitive
    // operations.
  })
  .catch(function(error) {
    // Some error occurred.
  });

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

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

// After asking the user for their email.
var email = window.prompt('Please provide your email');
firebase.auth().fetchSignInMethodsForEmail(email)
  .then(function(signInMethods) {
    // This returns the same array as fetchProvidersForEmail but for email
    // provider identified by 'password' string, signInMethods would contain 2
    // different strings:
    // 'emailLink' if the user previously signed in with an email/link
    // 'password' if the user has a password.
    // A user could have both.
    if (signInMethods.indexOf(
            firebase.auth.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD) != -1) {
      // User can sign in with email/password.
    }
     if (signInMethods.indexOf(
             firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD) != -1) {
       // User can sign in with email/link.
    }
  })
  .catch(function(error) {
    // Some error occurred, you can inspect the code: error.code
  });
}

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

次のステップ

ユーザーが初めてログインすると、新しいユーザー アカウントが作成され、ユーザーがログインに使用した認証情報(ユーザー名とパスワード、電話番号、または認証プロバイダ情報)にアカウントがリンクされます。この新しいアカウントは Firebase プロジェクトの一部として保存され、ユーザーのログイン方法にかかわらず、プロジェクトのすべてのアプリでユーザーを識別するために使用できます。

  • アプリでユーザーの認証ステータスを把握するには、Auth オブジェクトにオブザーバーを設定することをおすすめします。これによって、ユーザーの基本的なプロフィール情報を User オブジェクトから取得できます。ユーザーの管理についての記事をご覧ください。

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

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

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

firebase.auth().signOut().then(function() {
  // Sign-out successful.
}).catch(function(error) {
  // An error happened.
});

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。