使用 Firebase AuthCredential 重新驗證身分並修改特定資料

前言

設計開發 APP 時,使用 SharedPreferences 儲存登入狀態資料
卻發現在登入後,關閉 APP 或 APP 處於背景活動階段 大於 5分鐘
若用戶想 刪除帳戶更改電子郵件地址更改密碼
Firebase 會拒絕使用 UpdateEmailUpdatePassword 這類 method
於是乎,需要用到 AuthCredential 重新驗證身分


AuthCredential 範例

1
2
3
4
5
6
7
8
9
10
11
12
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

AuthCredential credential = EmailAuthProvider
.getCredential("user@example.com", "password1234");

user.reauthenticate(credential)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
Log.d(TAG, "User re-authenticated.");
}
});

在此範例中,使用字串形式的 user@example.compassword1234 來取得 AuthCredential。然而,這種方式的使用受到限制,並不適用於驗證不同的使用者。


解決辦法與程式碼展示

以下是我提出的解決方案,並展示了相關的程式碼。

使用者在修改資料前需輸入目前的密碼

1
2
3
4
5
6
7
8
9
final EditText checkPassword = new EditText(view.getContext());

final AlertDialog.Builder CheckDialog = new AlertDialog.Builder(view.getContext());

CheckDialog.setTitle("請輸入目前密碼");
CheckDialog.setView(checkPassword);
CheckDialog.setPositiveButton("確定", new DialogInterface.OnClickListener() {
....
}

在此處使用了一個 AlertDialog,使用者可以在對話方塊中輸入目前的密碼,然後按下確定鍵。

將取得的使用者資料應用於方法中

1
2
3
4
5
6
7
8
9
10
AuthCredential credential = EmailAuthProvider
.getCredential(mAuth.getCurrentUser().getEmail(), checkPassword.getText().toString());
mAuth.getCurrentUser().reauthenticate(credential).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()){
...
}else{
Toast.makeText(AccountActivity.this, "密碼輸入錯誤", Toast.LENGTH_SHORT).show();
}

email 使用 mAuth.getCurrentUser().getEmail() 取得的使用者目前email
password 使用 EditText checkPassword 取得的使用者目前password
if (task.isSuccessful()) 驗證成功就繼續執行,失敗則顯示密碼輸入錯誤

使用者輸入新的email

1
2
3
4
5
6
7
8
9
final EditText resetEmail = new EditText(view.getContext());

final AlertDialog.Builder ResetDialog = new AlertDialog.Builder(view.getContext());

ResetDialog.setTitle("請輸入新的電子郵件地址?");
ResetDialog.setView(resetEmail);
ResetDialog.setPositiveButton("確定", new DialogInterface.OnClickListener() {
....
}

再設置一個對話方塊,取得該對話方塊內的值並且按下確定鍵

請求重置的最後確認

1
2
3
4
5
6
final AlertDialog.Builder ConfirmDialog = new AlertDialog.Builder(view.getContext());

ConfirmDialog.setTitle("確定重置電子郵件地址");
ConfirmDialog.setPositiveButton("確定", new DialogInterface.OnClickListener() {
...
}

再設置一個對話方塊,確認使用者按下確定鍵

執行UpdateEmail

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String email = resetEmail.getText().toString();

mAuth.getCurrentUser().updateEmail(email)
.addOnCompleteListener(new OnCompleteListener<Void>() {

public void onComplete( Task<Void> task) {
if (task.isSuccessful()) {
mAuth.getCurrentUser().sendEmailVerification();
mAuth.signOut();
startActivity(new Intent(AccountActivity.this, LoginActivity.class));
finish();
Toast.makeText(AccountActivity.this, "更新電子郵件地址成功,請重新登錄", Toast.LENGTH_SHORT).show();
}
}
});

整合上述程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
private FirebaseAuth mAuth;

private Button updateEmail;

mAuth = FirebaseAuth.getInstance();

updateEmail = findViewById(R.id.updateEmail);

updateEmail.setOnClickListener(view -> {
final EditText checkPassword = new EditText(view.getContext());
final EditText resetEmail = new EditText(view.getContext());
final AlertDialog.Builder ResetDialog = new AlertDialog.Builder(view.getContext());
final AlertDialog.Builder CheckDialog = new AlertDialog.Builder(view.getContext());
final AlertDialog.Builder ConfirmDialog = new AlertDialog.Builder(view.getContext());
CheckDialog.setTitle("請輸入目前密碼");
CheckDialog.setView(checkPassword);
CheckDialog.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
AuthCredential credential = EmailAuthProvider
.getCredential(mAuth.getCurrentUser().getEmail(), checkPassword.getText().toString());
mAuth.getCurrentUser().reauthenticate(credential).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()){
ResetDialog.setTitle("請輸入新的電子郵件地址?");
ResetDialog.setView(resetEmail);
ResetDialog.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ConfirmDialog.setTitle("確定重置電子郵件地址");
ConfirmDialog.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
String email = resetEmail.getText().toString();
mAuth.getCurrentUser().updateEmail(email)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
mAuth.getCurrentUser().sendEmailVerification();
mAuth.signOut();
startActivity(new Intent(AccountActivity.this, LoginActivity.class));
finish();
Toast.makeText(AccountActivity.this, "更新電子郵件地址成功,請重新登錄", Toast.LENGTH_SHORT).show();
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
String errorCode = ((FirebaseAuthException) task.getException()).getErrorCode();
switch (errorCode) {
case "ERROR_INVALID_CUSTOM_TOKEN":
Toast.makeText(AccountActivity.this, "自定義token不正確。", Toast.LENGTH_LONG).show();
break;
case "ERROR_CUSTOM_TOKEN_MISMATCH":
Toast.makeText(AccountActivity.this, "自定義token屬於不同的用戶。", Toast.LENGTH_LONG).show();
break;
case "ERROR_INVALID_CREDENTIAL":
Toast.makeText(AccountActivity.this, "提供的身份驗證憑據格式不正確或已過期。", Toast.LENGTH_LONG).show();
break;
case "ERROR_INVALID_EMAIL":
Toast.makeText(AccountActivity.this, "電子郵件地址格式錯誤。", Toast.LENGTH_LONG).show();
break;
case "ERROR_WRONG_PASSWORD":
Toast.makeText(AccountActivity.this, "密碼不正確。", Toast.LENGTH_LONG).show();
break;
case "ERROR_USER_MISMATCH":
Toast.makeText(AccountActivity.this, "提供的憑證與先前登錄的用戶不相符。", Toast.LENGTH_LONG).show();
break;
case "ERROR_REQUIRES_RECENT_LOGIN":
Toast.makeText(AccountActivity.this, "需要最近的身份驗證。 在重試此請求之前再次登錄。", Toast.LENGTH_LONG).show();
break;
case "ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL":
Toast.makeText(AccountActivity.this, "已存在具有相同電子郵件地址但登錄憑證不同的帳戶。 使用與此電子郵件地址關聯的提供商登錄。", Toast.LENGTH_LONG).show();
break;
case "ERROR_EMAIL_ALREADY_IN_USE":
Toast.makeText(AccountActivity.this, "該電子郵件地址已被另一個帳戶使用。", Toast.LENGTH_LONG).show();
break;
case "ERROR_CREDENTIAL_ALREADY_IN_USE":
Toast.makeText(AccountActivity.this, "此憑證已與其他用戶帳戶相關聯。", Toast.LENGTH_LONG).show();
break;
case "ERROR_USER_DISABLED":
Toast.makeText(AccountActivity.this, "該用戶帳戶已被管理員禁用。", Toast.LENGTH_LONG).show();
break;
case "ERROR_USER_TOKEN_EXPIRED":
Toast.makeText(AccountActivity.this, "用戶的憑證不再有效。 用戶必須重新登錄。", Toast.LENGTH_LONG).show();
break;
case "ERROR_USER_NOT_FOUND":
Toast.makeText(AccountActivity.this, "為查詢到與此對應的用戶記錄, 該用戶可能已被刪除。", Toast.LENGTH_LONG).show();
break;
case "ERROR_OPERATION_NOT_ALLOWED":
Toast.makeText(AccountActivity.this, "不允許此操作。 您必須在控制台中啟用此服務。", Toast.LENGTH_LONG).show();
break;
case "ERROR_WEAK_PASSWORD":
Toast.makeText(AccountActivity.this, "輸入的密碼無效。", Toast.LENGTH_LONG).show();
break;
}
}
});
}
});
ConfirmDialog.setNegativeButton("取消",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
ConfirmDialog.create().show();
}
});
ResetDialog.setNegativeButton("取消",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
ResetDialog.create().show();
}else{
Toast.makeText(AccountActivity.this, "密碼輸入錯誤", Toast.LENGTH_SHORT).show();
}
}
});
}
});
CheckDialog.setNegativeButton("取消",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {

}
});
CheckDialog.create().show();
});