【问题标题】:I can't reauthenticate or revoke access token of the user in Firebase我无法在 Firebase 中重新验证或撤销用户的访问令牌
【发布时间】:2020-08-02 13:02:31
【问题描述】:

要求

1。我想让用户选择删除他/她的帐户,而现在用户可以使用 Google 和手机登录。

我阅读了一些文档,结果发现如果我可以重新验证用户身份,我可以轻松删除该帐户,但我无法做到这一点。

这是我用来重新验证帐户的代码

目前我只是在尝试使用 Google。

 final FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
    GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(getActivity());
    if(account != null && user != null) {
        AuthCredential credential = GoogleAuthProvider.getCredential(account.getIdToken(),null);
        user.reauthenticate(credential)
                .addOnSuccessListener(new OnSuccessListener<Void>() {
                    @Override
                    public void onSuccess(Void aVoid) {
                        Log.d(TAG,"reauthenticated");
                    }
                })

但是它会产生错误,即,

com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The supplied auth credential is malformed or has expired. [ ID Token issued at 1587271042 is stale to sign-in.

通过阅读一些文档,我也明白如果我没有错,这是因为令牌在一小时内有效,而我在一小时后尝试访问它。也就是说,为什么会出现这个错误?

我包含此代码,以便您可以告诉我另一种方法。

我也知道另一种方法,我试过了:

通过点击 删除帐户 按钮,我可以通过弹出一个 Google 帐户对话框来启动 Google 登录流程,以便用户可以再次登录,因为这将是一个全新的登录 - in,那么我可以只说user.delete(),它会删除该帐户,但这不是一个好的选择,原因有以下三个:

1用户会思考为什么他/她必须再次选择一个帐户

2 我无法更改该对话框的标题。它总是有标题选择帐户以继续“我的应用名称”,这并不反映我删除帐户的意图。

3用户不知道他/她必须选择当前登录的帐户,他/她可能会选择其他帐户

我不想通过将用户带到登录流程来打扰用户。我可以刷新令牌并立即删除帐户吗?

或者,如果没有任何方法并且用户必须再次登录,我可以使用 AuthUI 以某种方式执行此操作,因为它对用户和我来说都更方便,因为我不必实现自定义所有提供商的 UI?

与此相关的问题很多,答案为零。我希望这个不会属于那个类别。

【问题讨论】:

  • 您可以使用 admin sdk 来完成。当用户单击删除帐户按钮时。它会用适当的数据 ping 一个 API。然后将开始删除帐户的过程。粗略的方法是admin.auth.user(uId).delete。我不记得该方法的地址,但它存在。这就是你应该这样做的方式。不使用重新认证

标签: android firebase firebase-authentication


【解决方案1】:

使用过期的身份验证令牌将不允许您通过 Firebase 进行身份验证。所以首先你必须获得一个新的 ID 令牌。

如果您的设备上存储的GoogleSignInAccount 支持它(您有一个存储的刷新令牌),您应该能够使用silentSignIn() 获取一个新的 ID 令牌,然后您可以将其传递给 Firebase。

以下流程大致从 JavaScript 中删除。预计会出现拼写错误和错误,但它应该为您(或其他人)指明正确的方向。

public void deleteCurrentFirebaseUser() {
  final FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
  if (user == null) {
    // TODO: Throw error or show message to user
    return;
  }

  // STEP 1: Get a new ID token (using cached user info)
  Task<GoogleSignInAccount> task = mGoogleSignInClient.silentSignIn();
  task
    .continueWithTask(Continuation<GoogleSignInAccount, Task<AuthResult>>() {
      @Override
      public void then(Task<GoogleSignInAccount> silentSignInTask) {
        GoogleSignInAccount acct = silentSignInTask.getResult();
        // STEP 2: Use the new token to reauthenticate with Firebase
        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        return mAuth.reauthenticate(credential);
      }
    })
    .continueWithTask(Continuation<AuthResult, Task<Void>>() {
      @Override
      public void then(Task<AuthResult> firebaseSignInTask) {
        AuthResult result = firebaseSignInTask.getResult();
        // STEP 3: If successful, delete the user
        FirebaseUser user = result.getUser();
        return user.delete();
      }
    })
    .addOnCompleteListener(this, new OnCompleteListener<Void>() {
      @Override
      public void onComplete(@NonNull Task<Void> deleteUserTask) {
        // STEP 4: Handle success/errors
        if (task.isSuccessful()) {
          // The user was successfully deleted
          Log.d(TAG, "deleteCurrentFirebaseUser:success");
          // TODO: Go to sign-in screen
        } else {
          // The user was not deleted
          // Google sign in, Firebase sign in or Firebase delete user operation failed.
          Log.w(TAG, "deleteCurrentFirebaseUser:failure", task.getException());
          Snackbar.make(mBinding.mainLayout, "Failed to delete user.", Snackbar.LENGTH_SHORT).show();

          final Exception taskEx = task.getException();
          if (taskEx instanceof ApiException) {
            ApiException apiEx = (ApiException) taskEx;
            int googleSignInStatusCode = apiEx.getStatusCode();
            // TODO: Handle Google sign-in exception based on googleSignInStatusCode
            // e.g. GoogleSignInStatusCodes.SIGN_IN_REQUIRED means the user needs to do something to allow background sign-in.
          } else if (taskEx instanceof FirebaseAuthException) {
            // One of:
            //  - FirebaseAuthInvalidUserException (disabled/deleted user)
            //  - FirebaseAuthInvalidCredentialsException (token revoked/stale)
            //  - FirebaseAuthUserCollisionException (does the user already exist? - it is likely that Google Sign In wasn't originally used to create the matching account)
            //  - FirebaseAuthRecentLoginRequiredException (need to reauthenticate user - it shouldn't occur with this flow)

            FirebaseAuthException firebaseAuthEx = (FirebaseAuthException) taskEx;
            String errorCode = firebaseAuthEx.getErrorCode(); // Contains the reason for the exception
            String message = firebaseAuthEx.getMessage();
            // TODO: Handle Firebase Auth exception based on errorCode or more instanceof checks
          } else {
            // TODO: Handle unexpected exception
          }
        }
      }
    });
}

上述方法的替代方法是使用Callable Cloud Function,它使用Admin SDK's Delete User function,正如@example 所评论的那样。这是一个简单的实现(没有任何确认步骤):

exports.deleteMe = functions.https.onCall((data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
  }

  const uid = context.auth.uid;

  return admin.auth().deleteUser(uid)
    .then(() => {
      console.log('Successfully deleted user');
      return 'Success!';
    })
    .catch(error => {
      console.error('Error deleting user: ', error);
      throw new functions.https.HttpsError('internal', 'Failed to delete user.', error.code);
    });
});

将使用以下方法调用:

FirebaseFunctions.getInstance()
  .getHttpsCallable("deleteMe")
  .call()
  .continueWith(new Continuation<HttpsCallableResult, Void>() {
    @Override
    public void then(@NonNull Task<HttpsCallableResult> task) {
      if (task.isSuccessful()) {
        // deleted user!
      } else {
        // failed!
      }
    }
  });

如果您使用 Cloud Functions 方法,我强烈建议您在删除他们的帐户之前向用户的链接电子邮件地址发送一封确认电子邮件,以确保它不是坏人。以下是实现该目标所需的粗略草案:

exports.deleteMe = functions.https.onCall((data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
  }

  const uid = context.auth.uid;

  return getEmailsForUser(context.auth)
    .then(userEmails => {
      if (data.email) { // If an email was provided, use that
        if (!userEmails.all.includes(data.email)) { // Throw an error if the provided email isn't linked to this user
          throw new functions.https.HttpsError('failed-precondition', 'User is not linked to provided email.');
        }
        return sendAccountDeletionConfirmationEmail(uid, data.email);
      } else if (userEmails.primary) { // If available, send confirmation to primary email
        return sendAccountDeletionConfirmationEmail(uid, userEmails.primary);
      } else if (userEmails.token) { // If not present, try the authentication token's email
        return sendAccountDeletionConfirmationEmail(uid, userEmails.token);
      } else if (userEmails.all.length == 1) { // If not present but the user has only one linked email, try that
        // If not present, send confirmation to the authentication token's email
        return sendAccountDeletionConfirmationEmail(uid, userEmails.all[0]);
      } else {
        throw new functions.https.HttpsError('internal', 'User has multiple emails linked to their account. Please provide an email to use.');
      }
    })
    .then(destEmail => {
      return {message: 'Email was sent successfully!', email: email}
    });
});

exports.confirmDelete = functions.https.onRequest((req, res) => {
  const uid = request.params.uid;
  const token = request.params.token;
  const nextPath = request.params.next;

  if (!uid) {
    res.status(400).json({error: 'Missing uid parameter'});
    return;
  }

  if (!token) {
    res.status(400).json({error: 'Missing token parameter'});
    return;
  }

  return validateToken(uid, token)
    .then(() => admin.auth().deleteUser(uid))
    .then(() => {
      console.log('Successfully deleted user');
      res.redirect('https://your-app.firebaseapp.com' + (nextPath ? decodeURIComponent(nextPath) : ''));
    })
    .catch(error => {
      console.error('Error deleting user: ', error);
      res.json({error: 'Failed to delete user'});
    });
});

function getEmailsForUser(auth) {
  return admin.auth().getUser(auth.uid)
    .then(record => {
      // Used to create array of unique emails
      const linkedEmailsMap = {};

      record.providerData.forEach(provider => {
        if (provider.email) {
          linkedEmailsMap[provider.email] = true;
        }
      });

      return {
        primary: record.email,
        token: auth.token.email || undefined,
        all: Object.keys(linkedEmailsMap);
      }
    });
}

function sendAccountDeletionConfirmationEmail(uid, destEmail) {
  const token = 'oauhdfaskljfnasoildfma'; // TODO: Create URL SAFE token generation logic

  // 'confirmation-tokens' should have the rules: { ".read": false, ".write": false }
  return admin.database().ref('confirmation-tokens/'+uid).set(token)
    .then(() => {
      // Place the UID and token in the URL, and redirect to "/" when finished (next=%2F).
      const url = `https://your-app.firebaseapp.com/api/confirmDelete?uid=${uid}&${token}&next=%2F`;

      const emailBody = 'Please click <a href="' + url + '">here</a> to confirm account deletion.<br/><br/>Or you can copy "'+url+'" to your browser manually.';

      return sendEmail(destEmail, emailBody); // TODO: Create sendEmail
    })
    .then(() => destEmail);
}

function validateToken(uid, token) {
  return admin.database().ref('confirmation-tokens/'+uid).once('value')
    .then((snapshot) => {
      if (snapshot.val() !== token) {
        throw new Error('Token mismatch!');
      }
    });
}

【讨论】:

  • 感谢您的宝贵回答,我现在可以使用GoogleSignIn.getClient(getActivity(),gso).silentSignIn() 进行此操作,然后重新进行身份验证并删除,尽管我不知道此代码如何知道当前登录帐户的内容以静音签名在里面,你能告诉我吗
  • 虽然您提供的第二种方法更好,但不幸的是我无法做到这一点,因为云功能在 java 中不可用,而且我不知道它们可用的其他语言,但感谢您的指导,你能告诉我如何再次默默地删除使用电话身份验证登录的用户
  • 当您在应用程序中登录 Google 用户时,凭据将与您的应用程序相关联。 silentSignIn() 尝试重用这些相同的凭据。关于 Cloud Functions 的部分,对于客户端设备,您使用什么语言编写它们并不重要 - 使用 Javascript 为客户端编写 Cloud Functions 和 Java/Kotlin/etc 是完全可以的。跨度>
  • 对不起,我不明白 to a client device, it doesn't matter what language you use to write them in - it is perfectly fine to use Javascript to write the Cloud Functions and Java/Kotlin/etc 那里的部分 -> stackoverflow.com/questions/47573981/… 他们说我们只能在 node js 环境中的 javascript 中做
  • 我会改写:您用 JavaScript 编写云函数,这些函数在运行 Node.JS 的容器上运行。在客户端设备上运行的代码(即任何使用 Firebase 客户端 SDK 而不是服务器的东西 - android 应用程序/iOS 应用程序/网站/IoT 设备/等)可以用您想要的任何语言编写。如果您将 Java 用于您的应用程序,那么您不需要也使用 Java 来使用 Cloud Functions。 Java 应用程序可以与 JavaScript 云函数通信。
【解决方案2】:

我处理了同样的问题,它对我有用的是:

private GoogleSignInClient mGoogleSignInClient;

private FirebaseAuth mAuth = FirebaseAuth.getInstance();
GoogleSignInOptions gso = new
            GoogleSignInOptions.
                    Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.default_web_client_id))
            .requestEmail()
            .build();

mGoogleSignInClient = GoogleSignIn.getClient(this, gso);

// Use method silentSignIn to sign in without the choose Account Popup Dialog
mGoogleSignInClient.silentSignIn()
    .addOnCompleteListener(
        this,
        new OnCompleteListener<GoogleSignInAccount>() {
            @Override
            public void onComplete(@NonNull Task<GoogleSignInAccount> task) {

                GoogleSignInAccount acct = task.getResult();
                // Get credential and reauthenticate that Google Account
                AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
                mAuth.getCurrentUser().reauthenticate(credential).addOnCompleteListener(new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        if (task.isSuccessful()) {

                            // If reauthentication is completed, then delete the Firebase user
                            mAuth.getCurrentUser().delete()
                                .addOnCompleteListener(new OnCompleteListener<Void>() {
                                    @Override
                                    public void onComplete(@NonNull Task<Void> task) {
                                        if (task.isSuccessful()) {
                                            Intent goToSignIn = new Intent(UpdateInfoActivity.this, SignIn.class);
                                            startActivity(goToSignIn);
                                        } // End if
                                    } // End onComplete
                                });
                        }
                    } // End onComplete
                });
            } // End onComplete
        });

【讨论】:

    猜你喜欢
    • 2019-04-12
    • 2020-09-29
    • 2015-06-02
    • 2018-04-23
    • 2015-03-06
    • 1970-01-01
    • 2019-11-26
    • 1970-01-01
    相关资源
    最近更新 更多