如何使用Firebase登录多个社交服务?

时间:2013-03-01 00:06:42

标签: authentication facebook-authentication firebase firebase-security

我希望用户能够使用多个不同的身份验证提供程序(例如Facebook,Twitter或Github)对我的Firebase应用程序进行身份验证。经过身份验证后,我希望用户无论使用哪种身份验证方法都可以访问同一帐户。

换句话说,我想将多个身份验证方法合并到我的应用程序中的单个帐户中。如何在Firebase应用中执行此操作?

4 个答案:

答案 0 :(得分:56)


更新(20160521): Firebase刚刚发布了对其Firebase Authentication产品的重大更新,该产品现在允许单个用户链接来自各种支持的提供商的帐户。要了解有关此功能的更多信息,请阅读iOSWebAndroid的文档。以下答案是出于历史原因。


核心Firebase服务提供了多种身份验证方法: https://www.firebase.com/docs/security/authentication.html

Firebase的核心是使用安全的JWT令牌进行身份验证。导致生成JWT令牌的任何内容(例如在您自己的服务器上使用JWT库)都可以对Firebase的用户进行身份验证,因此您可以完全控制身份验证过程。

Firebase提供名为Firebase Simple Login的服务,这是生成这些令牌的一种方式(这提供了我们的Facebook,Twitter等身份验证)。它适用于常见的身份验证方案,因此您可以在没有服务器的情况下快速启动并运行,但它不是唯一的身份验证方法,并且不是一个全面的解决方案。

以下是允许使用Firebase Simple Login登录多个提供商的一种方法:

  1. 为每个用户存储一个规范用户标识符,以及映射 每个特定于提供者的标识符,用于该规范标识。
  2. 更新您的安全规则以匹配上的任何凭据 给定用户帐户,而不只是一个。
  3. 在实践中,安全规则可能如下所示,假设您要启用Twitter和Facebook身份验证(或允许用户创建一个帐户,然后再添加另一个):

    {
      "users": {
        "$userid": {
          // Require the user to be logged in, and make sure their current credentials
          // match at least one of the credentials listed below, unless we're creating
          // a new account from scratch.
          ".write": "auth != null && 
            (data.val() === null || 
            (auth.provider === 'facebook' && auth.id === data.child('facebook/id').val() || 
            (auth.provider === 'twitter' && auth.id === data.child('twitter/id').val()))"
        }
      },
      "user-mappings": {
        // Only allow users to read the user id mapping for their own account.
        "facebook": {
          "$fbuid": {
            ".read": "auth != null && auth.provider === 'facebook' && auth.id === $fbuid",
            ".write": "auth != null && 
              (data.val() == null || 
              root.child('users').child(data.val()).child('facebook-id').val() == auth.id)"
          }
        },
        "twitter": {
          "$twuid": {
            ".read": "auth != null && auth.provider === 'twitter' && auth.id === $twuid",
            ".write": "auth != null && 
              (data.val() == null || 
              root.child('users').child(data.val()).child('twitter-id').val() == auth.id)"
          }
        }
      }
    }
    

    在此示例中,您存储一个全局用户ID(可以是您选择的任何内容),并维护Facebook,Twitter等对主用户记录的身份验证机制之间的映射。在为每个用户登录后,您将从用户映射中获取主用户记录,并将该id用作用户数据和操作的主存储。以上还限制和验证用户映射中的数据,以便它只能由在/ users / $ userid /(facebook-id | twitter)下已经具有相同Facebook,Twitter等用户ID的适当用户写入。 -id |等-ID)

    此方法可让您快速启动并运行。但是,如果您有复杂的用例并希望完全控制身份验证体验,则可以在自己的服务器上运行自己的身份验证代码。您可以使用许多有用的开源库来执行此操作,例如everyauthpassport

    您还可以使用第三方身份验证提供程序进行身份验证。例如,您可以使用Singly,它具有开箱即用的各种集成,而无需编写任何服务器端代码。

答案 1 :(得分:16)

我知道这篇文章存在了几个月但是当我遇到这个问题时,我花了很多时间来使代码更加灵活。根据上面的安德鲁代码,我稍微调整了一下代码。

样本数据存储:

userMappings
|---facebook:777
|   |---user:"123"
|---twitter:888
    |---user:"123"
users
|---123
    |---userMappings
        |---facebook: "facebook:777"
        |---twitter: "twitter:888"

安全规则:

"userMappings": {
  "$login_id": {
    ".read": "$login_id === auth.uid",
    ".write": "auth!== null && (data.val() === null || $login_id === auth.uid)"
  }
},

"users": {
  "$user_id": {
    ".read": "data.child('userMappings/'+auth.provider).val()===auth.uid",
    ".write": "auth!= null && (data.val() === null || data.child('userMappings/'+auth.provider).val()===auth.uid)"
  }
}

所以 userMappings 仍然是我们通过Facebook,Twitter登录时查看的第一个信息.... userMappings' 用户将指向用户中的主要帐户。因此,在通过Facebook或Twitter登录后,我们可以查找主用户帐户。在用户中,我们保留了可以访问其数据的userMapping列表。

创建新用户时,我们必须先在用户中创建一个帐户。 用户中的用户ID可以是我们想要的任何内容。这很灵活,因为我们可以提供更多的登录方法,如Google,Github,而无需添加更多安全规则。

答案 2 :(得分:2)

我刚刚创建了一个angularfire装饰器来处理这个问题:angularfire-multi-auth

答案 3 :(得分:0)

我花了很长时间思考一个好的解决方案,并且恕我直言,能够从任何提供商注册只是令人困惑。在我的前端,我总是要求进行电子邮件注册,例如,登录facebook和google +的用户在通知他的电子邮件时将被记录为同一用户。

这样,Kuma提出的Sample Data不需要复制userMappings。

示例数据存储:

public DatePicker initMonthPicker(){
    DatePicker dp_mes = new DatePicker(new ContextThemeWrapper(getActivity(), android.R.style.Theme_Holo_Light_Dialog_NoActionBar));

    int year    = dp_mes.getYear();
    int month   = dp_mes.getMonth();
    int day     = dp_mes.getDayOfMonth();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
        int daySpinnerId = Resources.getSystem().getIdentifier("day", "id", "android");
        if (daySpinnerId != 0)
        {
            View daySpinner = dp_mes.findViewById(daySpinnerId);
            if (daySpinner != null)
            {
                daySpinner.setVisibility(View.VISIBLE);
            }
        }

        int monthSpinnerId = Resources.getSystem().getIdentifier("month", "id", "android");
        if (monthSpinnerId != 0)
        {
            View monthSpinner = dp_mes.findViewById(monthSpinnerId);
            if (monthSpinner != null)
            {
                monthSpinner.setVisibility(View.VISIBLE);
            }
        }

        int yearSpinnerId = Resources.getSystem().getIdentifier("year", "id", "android");
        if (yearSpinnerId != 0)
        {
            View yearSpinner = dp_mes.findViewById(yearSpinnerId);
            if (yearSpinner != null)
            {
                yearSpinner.setVisibility(View.GONE);
            }
        }
    } else { //Older SDK versions
        Field f[] = dp_mes.getClass().getDeclaredFields();
        for (Field field : f)
        {
            if(field.getName().equals("mDayPicker") || field.getName().equals("mDaySpinner"))
            {
                field.setAccessible(true);
                Object dayPicker = null;
                try {
                    dayPicker = field.get(dp_mes);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                ((View) dayPicker).setVisibility(View.GONE);
            }

            if(field.getName().equals("mMonthPicker") || field.getName().equals("mMonthSpinner"))
            {
                field.setAccessible(true);
                Object monthPicker = null;
                try {
                    monthPicker = field.get(dp_mes);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                ((View) monthPicker).setVisibility(View.VISIBLE);
            }

            if(field.getName().equals("mYearPicker") || field.getName().equals("mYearSpinner"))
            {
                field.setAccessible(true);
                Object yearPicker = null;
                try {
                    yearPicker = field.get(dp_mes);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                ((View) yearPicker).setVisibility(View.GONE);
            }
        }
    }
    return dp_mes;
}