Skip to content

Commit 6e85974

Browse files
committed
Expose TOKEN_EXPIRED error upon mfa unenroll.
This can be thrown if the MFA option that was most recently enrolled into, was unenrolled. The user will be logged out to prove the posession of the other second factor. This error can be handled by reauthenticating the user. This change also updates the demo app to store the lastUser in case mfa unenroll logs out the user. From here, the lastUser can be reauthenticated.
1 parent 49ee786 commit 6e85974

File tree

4 files changed

+40
-9
lines changed

4 files changed

+40
-9
lines changed

packages/auth/demo/public/index.html

+4
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@
252252
id="sign-in-with-email-and-password">
253253
Sign In with Email and Password
254254
</button>
255+
<button class="btn btn-block btn-primary"
256+
id="reauth-with-email-and-password">
257+
Reauthenticate with Email and Password
258+
</button>
255259
</form>
256260
<form class="form form-bordered no-submit">
257261
<input type="text" id="user-custom-token" class="form-control"

packages/auth/demo/src/index.js

+32-2
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ function showMultiFactorStatus(activeUser) {
247247
const label = info && (info.displayName || info.uid);
248248
if (label) {
249249
$('#enrolled-factors-drop-down').removeClass('open');
250+
setLastUser(activeUser);
250251
mfaUser.unenroll(info).then(() => {
251252
refreshUserData();
252253
alertSuccess('Multi-factor successfully unenrolled.');
@@ -278,6 +279,10 @@ function onAuthError(error) {
278279
handleMultiFactorSignIn(getMultiFactorResolver(auth, error));
279280
} else {
280281
alertError('Error: ' + error.code);
282+
if (error.code === 'auth/user-token-expired') {
283+
console.log(lastUser);
284+
alertError("Token expired, please reauthenticate.");
285+
}
281286
}
282287
}
283288

@@ -403,13 +408,37 @@ function onLinkWithEmailLink() {
403408
* Re-authenticate a user with email link credential.
404409
*/
405410
function onReauthenticateWithEmailLink() {
411+
if (!activeUser()) {
412+
alertError('No user logged in. Select the "Last User" tab to reauth the previous user.');
413+
return;
414+
}
406415
const email = $('#link-with-email-link-email').val();
407416
const link = $('#link-with-email-link-link').val() || undefined;
408417
const credential = EmailAuthProvider.credentialWithLink(email, link);
418+
// This will not set auth.currentUser to lastUser if the lastUser is reauthenticated.
409419
reauthenticateWithCredential(activeUser(), credential).then(result => {
410420
logAdditionalUserInfo(result);
411421
refreshUserData();
412-
alertSuccess('User reauthenticated!');
422+
alertSuccess('User reauthenticated with email link!');
423+
}, onAuthError);
424+
}
425+
426+
/**
427+
* Re-authenticate a user with email and password.
428+
*/
429+
function onReauthenticateWithEmailAndPassword() {
430+
if (!activeUser()) {
431+
alertError('No user logged in. Select the "Last User" tab to reauth the previous user.');
432+
return;
433+
}
434+
const email = $('#signin-email').val();
435+
const password = $('#signin-password').val();
436+
const credential = EmailAuthProvider.credential(email, password);
437+
// This will not set auth.currentUser to lastUser if the lastUser is reauthenticated.
438+
reauthenticateWithCredential(activeUser(), credential).then(result => {
439+
logAdditionalUserInfo(result);
440+
refreshUserData();
441+
alertSuccess('User reauthenticated with email/password!');
413442
}, onAuthError);
414443
}
415444

@@ -1264,7 +1293,7 @@ function signInWithPopupRedirect(provider) {
12641293
break;
12651294
case 'reauthenticate':
12661295
if (!activeUser()) {
1267-
alertError('No user logged in.');
1296+
alertError('No user logged in. Select the "Last User" tab to reauth the previous user.');
12681297
return;
12691298
}
12701299
inst = activeUser();
@@ -1860,6 +1889,7 @@ function initApp() {
18601889
// Actions listeners.
18611890
$('#sign-up-with-email-and-password').click(onSignUp);
18621891
$('#sign-in-with-email-and-password').click(onSignInWithEmailAndPassword);
1892+
$('#reauth-with-email-and-password').click(onReauthenticateWithEmailAndPassword);
18631893
$('.sign-in-with-custom-token').click(onSignInWithCustomToken);
18641894
$('#sign-in-anonymously').click(onSignInAnonymously);
18651895
$('#sign-in-with-generic-idp-credential').click(

packages/auth/src/core/strategies/credential.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ export async function linkWithCredential(
9696
*
9797
* @remarks
9898
* Use before operations such as {@link updatePassword} that require tokens from recent sign-in
99-
* attempts. This method can be used to recover from a `CREDENTIAL_TOO_OLD_LOGIN_AGAIN` error.
99+
* attempts. This method can be used to recover from a `CREDENTIAL_TOO_OLD_LOGIN_AGAIN` error
100+
* or a `TOKEN_EXPIRED` error.
100101
*
101102
* @param user - The user.
102103
* @param credential - The auth credential.

packages/auth/src/mfa/mfa_user.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class MultiFactorUserImpl implements MultiFactorUser {
7878
const mfaEnrollmentId =
7979
typeof infoOrUid === 'string' ? infoOrUid : infoOrUid.uid;
8080
const idToken = await this.user.getIdToken();
81+
try {
8182
const idTokenResponse = await _logoutIfInvalidated(
8283
this.user,
8384
withdrawMfa(this.user.auth, {
@@ -94,14 +95,9 @@ export class MultiFactorUserImpl implements MultiFactorUser {
9495
// are now invalid), reloading the user will discover this and invalidate
9596
// the user's state accordingly.
9697
await this.user._updateTokensIfNecessary(idTokenResponse);
97-
try {
98-
await this.user.reload();
98+
await this.user.reload();
9999
} catch (e) {
100-
if (
101-
(e as FirebaseError)?.code !== `auth/${AuthErrorCode.TOKEN_EXPIRED}`
102-
) {
103100
throw e;
104-
}
105101
}
106102
}
107103
}

0 commit comments

Comments
 (0)