1

我有一个包含所有个人信息(姓名、名字、出生日期、电子邮件等)的人员列表。我想向每个人发送一封电子邮件,其中包含一个链接,一旦点击,他们就可以直接访问在我们的网站上连接。无需输入密码。


我按照 Firebase 程序进行无密码身份验证:

但是大多数示例不太适合我的用例。

大多数例子:

  1. 用户访问您的网站,要求进行无密码身份验证,输入他的电子邮件,(电子邮件存储在window.location.href
  2. 用户收到一封带有登录链接的电子邮件,他点击它
  3. 用户在您的网站上,已登录(感谢他存储在 中的电子邮件window.location.href)。

我的用例:

  1. 没有任何。我已经有了用户的电子邮件,所以我直接将链接发送给他以进行连接。
  2. 用户收到一封带有登录链接的电子邮件,他点击它
  3. 用户在我的网站上,但必须在提示符中再次输入他的电子邮件(因为它显然没有存储在 中window.location.href)。

在我的情况下,该window.location.href变量将永远不会被使用。而且我不希望我的用户在单击链接后必须重新输入他的电子邮件。既然我已经有了他的email,为什么还要再问他呢?


那么如何跳过这一步呢?这样做有什么安全风险吗?


到目前为止,这是我的代码:

后退:

import firebase_admin
from firebase_admin import auth
from google.cloud import firestore


def create_new_auth(dictionary):
    user = auth.create_user(
        email=dictionary['email'],
        email_verified=True,
        phone_number=dictionary['phone'],
        password='super_secure_password_007',
        display_name=f"{dictionary['firstName']} {dictionary['lastName']}",
        disabled=False)
    print('Sucessfully created new user: {0}'.format(user.uid))

    return user.uid


def create_new_pre_user(db, dictionary, uid):
    dictionary = {
        'uid': uid,
        'email': dictionary['email'],
        'lastName': dictionary['lastName'],
        'gender': dictionary['gender'],
        'birthday': dictionary['birthday'],
        'phone': dictionary['phone'],
        'firstName': dictionary['firstName']
    }
    db.collection(u'users').document(uid).set(dictionary)


def main(dictionary):
    firebase_admin.initialize_app()
    db = firestore.Client()
    uid = create_new_auth(dictionary)
    create_new_pre_user(db, dictionary, uid)

    action_code_settings = auth.ActionCodeSettings(
        url=f'http://localhost:4200/login',
        handle_code_in_app=True,
        ios_bundle_id='com.example.ios',
        android_package_name='com.example.android',
        android_install_app=True,
        android_minimum_version='12',
        dynamic_link_domain='magic42.page.link',
    )

    link = auth.generate_sign_in_with_email_link(dictionary['email'], action_code_settings)


if __name__ == '__main__':
    dictionary = {
        "firstName": "Jone",
        "lastName": "Doe",
        "birthday": 12345678,
        "gender": "male",
        "email": "john.doe@gmail.com",
        "phone": "+33611223344"
    }
    main(dictionary)

正面:

private signInWithEmail() {
    if (this.authService.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.
      let 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.
      this.authService.signInWithEmailLink(email, window.location.href)
        .then((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
          this.router.navigate(['/patient', 'quiz'])
        })
        .catch((error) => {
          // Some error occurred, you can inspect the code: error.code
          // Common errors could be invalid email and invalid or expired OTPs.
        });
    }
  }
isSignInWithEmailLink(href) {
    return this.afAuth.auth.isSignInWithEmailLink(href);
  }

  signInWithEmailLink(email: string, href: string) {
    return this.afAuth.auth.signInWithEmailLink(email, href)
  }

编辑


问题是,当他第一次使用该链接访问我们的网站时,前台不知道用户的电子邮件。有一种方法可以将email信息从我们的服务器端传递到前端,但在 URL 中很清楚:根据 Firebase 本身(链接) ,这是有风险的并且不是一个好的做法

像这样:

def main(dictionary):
    firebase_admin.initialize_app()
    db = firestore.Client()
    uid = create_new_auth(dictionary)
    create_new_pre_user(db, dictionary, uid)

    action_code_settings = auth.ActionCodeSettings(
        url=f'http://localhost:4200/login/?email=john.doe@gmail.com',
        handle_code_in_app=True,
        ios_bundle_id='com.example.ios',
        android_package_name='com.example.android',
        android_install_app=True,
        android_minimum_version='12',
        dynamic_link_domain='magic42.page.link',
    )

    link = auth.generate_sign_in_with_email_link(dictionary['email'], action_code_settings)

那么如何将email信息从后面传递到前面,以便用户在单击我的“魔术链接”后重定向到我的网站时不必再次输入?

4

3 回答 3

2

您可以做的一件事是在后端创建一个一次性令牌,该令牌链接到您的用户的电子邮件(或链接到 Firestore 中的文档),并将其放在 url 中。当用户进入页面时,使用令牌(可能只是一个简单的 uuid)调用您的后端,并让您的后端让用户登录,然后使该令牌失效/不再使用。

例如

https://yoursite.com/44ed3716-2b8f-4068-a445-b05a8fee17c3

前端发送44ed3716-2b8f-4068-a445-b05a8fee17c3到后端......后端看到令牌,将它们登录,然后使该令牌不再有效。

更新

要在下面的评论中回答您关于不再需要通过 firebase 进行电子邮件链接身份验证的问题:不一定。那时,您正在创建自己的电子邮件登录系统(实际上并不太难),并且在某种程度上重新发明了轮子。向 url 添加令牌只是一种将用户与电子邮件相关联的方法,而无需将电子邮件实际放入 url 中,这样您的前端就可以在单击链接后知道用户是谁。后端向您发送电子邮件后,您可以将其存储在本地存储并正常使用 firebase 完成登录。

于 2021-08-11T16:35:51.870 回答
0

要求用户输入他/她的电子邮件而不是将其存储在存储中并没有安全风险window,有人可能会争辩说这样做实际上安全。也就是说,您如何才能做到这一点:

  1. 确保您已启用电子邮件无密码身份验证。
  2. 使用管理 SDK,将每个电子邮件地址添加到您的身份验证表中(尽管我不会设置emailVerified: true- 当他们单击魔术链接并在登录时验证自己时会发生这种情况。
  3. 再次使用 admin SDK,为每个用户生成一个魔术链接并将其发送给他们。
  4. 在您的登录页面(魔术链接将他们带到那里),提示他们输入他们的电子邮件地址,然后将其与魔术链接一起使用以进行身份​​验证。Firebase 提供的示例代码向您展示了如何在if(!email){ ... }代码部分执行此操作,其中它使用窗口提示来收集用户的电子邮件,以防用户单击单独设备上的链接或浏览器没有/不能存储电子邮件地址。
于 2021-08-09T11:47:00.390 回答
0

如果您已经拥有用户的电子邮件,则可以firebase.auth().sendSignInLinkToEmail在客户端 JavaScript SDK ( docs ) 或generateSignInWithEmailLink服务器端 Node.js SDK ( docs ) 中使用该电子邮件进行调用。这两个调用都将用户的电子邮件作为参数。


一旦用户在单击链接后登陆您的网站,您就可以使用如下的身份验证状态侦听器访问他们的个人资料

firebase.auth().onAuthStateChanged((user) => {
  if (user) {
    var uid = user.uid;
    var email = user.email;
  }
});
于 2021-08-09T17:06:09.040 回答