从 Flutter 为 Firebase 身份验证设置自定义声明

Posted

技术标签:

【中文标题】从 Flutter 为 Firebase 身份验证设置自定义声明【英文标题】:Setting custom claims for Firebase auth from flutter 【发布时间】:2020-06-28 15:06:58 【问题描述】:

我正在为应用使用 Firebase 身份验证,但作为用户创建的一部分,我需要设置一些自定义声明。

我编写了一个云函数来在创建用户时设置声明:

    const functions = require('firebase-functions');
    const admin = require('firebase-admin');
    admin.initializeApp(functions.config().firebase);

    // On sign up.
    exports.processSignUp = functions.auth.user().onCreate(user => 

    let customClaims;

    // Set custom user claims on this newly created user.
    return admin.auth().setCustomUserClaims(user.uid, 
            'https://hasura.io/jwt/claims': 
                'x-hasura-default-role': 'user',
                'x-hasura-allowed-roles': ['user'],
                'x-hasura-user-id': user.uid
            
        )
        .then(() => 
            // Update real-time database to notify client to force refresh.
            const metadataRef = admin.database().ref("metadata/" + user.uid);
            // Set the refresh time to the current UTC timestamp.
            // This will be captured on the client to force a token refresh.
            return metadataRef.set(
                refreshTime: new Date().getTime()
            );
        )
        .then(() => 
            return admin.auth().getUser(user.uid);
        )
        .then(userRecord => 
            console.log(userRecord);
            return userRecord.toJSON();

        )
        .catch(error => 
            console.log(error);
        );
);

当我将 userRecord 打印到控制台时,我可以看到自定义声明设置正确。

然后在颤振中我从创建的用户那里获得令牌,但它似乎没有附加自定义声明。

我正在使用此代码创建用户并在 Flutter 中打印声明

    Future<FirebaseUser> signUp(String email, String password) async 
    final FirebaseUser user = (await auth.createUserWithEmailAndPassword(
      email: email,
      password: password,
    )).user;

    IdTokenResult result = await (user.getIdToken(refresh: true));
    print('claims : $result.claims');

    return user;
  

如果我在 jwt 调试器中检查令牌本身,我可以看到它没有得到自定义声明。 设置声明后,我是否需要一些额外的步骤来尝试获取更新的令牌? 我试过 user.reload()user.getIdToken(refresh: true) 但它们似乎没有帮助。

关于如何获取具有自定义声明的令牌的任何想法?

【问题讨论】:

我通过调用 AuthResult result = await auth.signInWithEmailAndPassword(email: email, password: password);在获得索赔之前意味着我确实获得了自定义索赔。使用电子邮件和密码登录时这很好,但我不知道在使用其他身份验证提供商(例如 google/facebook)登录时我会如何做。 【参考方案1】:

为了将来参考,我设法按照 Doug 的建议完成了这项工作。

这是我的 firebase sdk 管理功能。

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

const firestore = admin.firestore();
const settings = timestampsInSnapshots: true;
firestore.settings(settings);


// On sign up.
exports.processSignUp = functions.auth.user().onCreate(async user => 

// Check if user meets role criteria:
// Your custom logic here: to decide what roles and other `x-hasura-*` should the user get
let customClaims;
// Set custom user claims on this newly created user.

return admin.auth().setCustomUserClaims(user.uid, 
    'https://hasura.io/jwt/claims': 
    'x-hasura-default-role': 'user',
    'x-hasura-allowed-roles': ['user'],
    'x-hasura-user-id': user.uid

  )
.then(async () => 
    await firestore.collection('users').doc(user.uid).set(
        createdAt: admin.firestore.FieldValue.serverTimestamp()
    );
 )
 .catch(error => 
    console.log(error);
 );
);

然后在事情的颤振方面

Future<FirebaseUser> signUp(String email, String password) async 
    final FirebaseUser user = (await auth.createUserWithEmailAndPassword(
      email: email,
      password: password,
    )).user;
    currentUser = user;

    await waitForCustomClaims();
    return user;


Future waitForCustomClaims() async 
    DocumentReference userDocRef = 
    Firestore.instance.collection('users').document(currentUser.uid);
    Stream<DocumentSnapshot> docs = userDocRef.snapshots(includeMetadataChanges: false);

    DocumentSnapshot data = await docs.firstWhere((DocumentSnapshot snapshot) => snapshot?.data !=null && snapshot.data.containsKey('createdAt'));  
    print('data $data.toString()');

    IdTokenResult idTokenResult = await (currentUser.getIdToken(refresh: true));
    print('claims : $idTokenResult.claims');

希望这将帮助其他希望做类似的人。

【讨论】:

这让我走上了正轨。很好的解决方案。我认为它现在可能有点过时了,尤其是对于 nullSafety2.0。这也应该真正包含在 try/catch 中。 此外,使用 Firestore 可能会因这种方法而产生极大的延迟。有时我已经等待了整整一分钟......最好将此方法与实时数据库一起使用,这样您的用户就不会在加载屏幕上等待几分钟。我采用了这种方法来使用实时数据库而不是 Firestore,它可以完美运行。 @BBish937 能否分享一下如何使用实时数据库监听客户端以获取云功能执行完成的更新。【参考方案2】:

您显示的代码可能是在创建帐户后过早尝试获取自定义声明。调用auth.createUserWithEmailAndPassword 后,该功能需要几秒钟才能触发。它是异步运行的,根本不会阻止用户创建过程。因此,在调用 user.getIdToken(refresh: true) 之前,您需要等待函数完成。

这正是我在this blog post 中提到的内容。我提供的解决方案如下:

    客户端:创建用户 客户端:等待在 Firestore 中创建具有用户 UID 的文档 服务器:Auth onCreate 函数触发器 服务器:函数发挥作用 服务器:最后,函数使用新用户的 UID 将数据写入新文档 客户端:数据库侦听器在创建文档时触发

然后,您将在客户端上添加更多步骤以在客户端看到新文档后刷新 ID 令牌。

帖子中给出的代码适用于 web/javascript,但该过程适用于任何客户端。您只需要让客户端等待功能完成,Firestore 是中继该信息的便捷场所,因为客户端可以实时收听。

另外read this post 可以让客户端根据写入 Firestore 文档的声明立即刷新其令牌。

底线是您需要在客户端和服务器之间同步大量代码。

【讨论】:

谢谢您,我会仔细阅读并消化它。作为一个快速测试,我在刷新令牌之前让客户端应用程序休眠了 10 秒钟,并且可以看到它确实可以工作,所以事情看起来很有希望 :) 是的,这就是我所期望的。尽管睡眠不会 100% 可靠,这就是为什么我选择将 Firestore 文档放在中间以获得更即时的反馈,无论该过程需要多长时间。 当然,睡觉是一种快速测试的方法。看来我还有一些工作要做,谢谢 您的博文有一个好主意!感谢分享

以上是关于从 Flutter 为 Firebase 身份验证设置自定义声明的主要内容,如果未能解决你的问题,请参考以下文章

在 Flutter 中使用 StreamProvider 4.0.1 从 Firebase 监控身份验证状态

用于 Flutter 桌面嵌入的 Firebase 身份验证插件

请求的身份验证范围不足 - 在 Flutter 中使用 Firebase 从 Google 登录获取生日时出错

为最终用户处理 Flutter 上的 Firebase 身份验证错误

我是 Flutter Web 的新手,如何使用 Firebase 电话身份验证对用户进行身份验证,有没有办法让用户保持登录状态?

使用存储和 cookie(不使用 Firebase)在 Flutter 中进行身份验证?