Skip to content

Commit b2163b3

Browse files
Expose INVALID_LOGIN_CREDENTIALS as auth/invalid-credential error. (#7772)
* Expose INVALID_LOGIN_CREDENTIALS as auth/invalid-credential error. Update the doc snippets for various SDK methods to explain the behavior when Email Enumeration Protection is enabled. Mark fetchSignInMethodsForEmail and updateEmail as deprecated. Update the demo app to use the error code. Fix error message for the error code and update tests. * Fix error message in newly added token revocation test. * Update packages/auth/src/core/strategies/email_and_password.ts Co-authored-by: Kevin Cheung <[email protected]> * address review comments. * Regenerate docs. --------- Co-authored-by: Kevin Cheung <[email protected]>
1 parent 2a2e2b7 commit b2163b3

File tree

12 files changed

+46
-18
lines changed

12 files changed

+46
-18
lines changed

.changeset/silly-islands-join.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/auth': patch
3+
---
4+
5+
Exposed INVALID_LOGIN_CREDENTIALS as auth/invalid-credential error and updated docs for various auth SDK methods.

common/api-review/auth.api.md

+1
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ export const AuthErrorCodes: {
165165
readonly INVALID_EMAIL: "auth/invalid-email";
166166
readonly INVALID_EMULATOR_SCHEME: "auth/invalid-emulator-scheme";
167167
readonly INVALID_IDP_RESPONSE: "auth/invalid-credential";
168+
readonly INVALID_LOGIN_CREDENTIALS: "auth/invalid-credential";
168169
readonly INVALID_MESSAGE_PAYLOAD: "auth/invalid-message-payload";
169170
readonly INVALID_MFA_SESSION: "auth/invalid-multi-factor-session";
170171
readonly INVALID_OAUTH_CLIENT_ID: "auth/invalid-oauth-client-id";

docs-devsite/auth.md

+8-7
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ Firebase Authentication
2828
| [confirmPasswordReset(auth, oobCode, newPassword)](./auth.md#confirmpasswordreset) | Completes the password reset process, given a confirmation code and new password. |
2929
| [connectAuthEmulator(auth, url, options)](./auth.md#connectauthemulator) | Changes the [Auth](./auth.auth.md#auth_interface) instance to communicate with the Firebase Auth Emulator, instead of production Firebase Auth services. |
3030
| [createUserWithEmailAndPassword(auth, email, password)](./auth.md#createuserwithemailandpassword) | Creates a new user account associated with the specified email address and password. |
31-
| [fetchSignInMethodsForEmail(auth, email)](./auth.md#fetchsigninmethodsforemail) | Gets the list of possible sign in methods for the given email address. |
31+
| [fetchSignInMethodsForEmail(auth, email)](./auth.md#fetchsigninmethodsforemail) | Gets the list of possible sign in methods for the given email address. This method returns an empty list when \[Email Enumeration Protection\](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled, irrespective of the number of authentication methods available for the given email. |
3232
| [getMultiFactorResolver(auth, error)](./auth.md#getmultifactorresolver) | Provides a [MultiFactorResolver](./auth.multifactorresolver.md#multifactorresolver_interface) suitable for completion of a multi-factor flow. |
3333
| [getRedirectResult(auth, resolver)](./auth.md#getredirectresult) | Returns a [UserCredential](./auth.usercredential.md#usercredential_interface) from the redirect-based sign-in flow. |
3434
| [initializeRecaptchaConfig(auth)](./auth.md#initializerecaptchaconfig) | Loads the reCAPTCHA configuration into the <code>Auth</code> instance. |
3535
| [isSignInWithEmailLink(auth, emailLink)](./auth.md#issigninwithemaillink) | Checks if an incoming link is a sign-in with email link suitable for [signInWithEmailLink()](./auth.md#signinwithemaillink)<!-- -->. |
3636
| [onAuthStateChanged(auth, nextOrObserver, error, completed)](./auth.md#onauthstatechanged) | Adds an observer for changes to the user's sign-in state. |
3737
| [onIdTokenChanged(auth, nextOrObserver, error, completed)](./auth.md#onidtokenchanged) | Adds an observer for changes to the signed-in user's ID token. |
3838
| [revokeAccessToken(auth, token)](./auth.md#revokeaccesstoken) | Revokes the given access token. Currently only supports Apple OAuth access tokens. |
39-
| [sendPasswordResetEmail(auth, email, actionCodeSettings)](./auth.md#sendpasswordresetemail) | Sends a password reset email to the given email address. |
39+
| [sendPasswordResetEmail(auth, email, actionCodeSettings)](./auth.md#sendpasswordresetemail) | Sends a password reset email to the given email address. This method does not throw an error when there's no user account with the given email address and \[Email Enumeration Protection\](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled. |
4040
| [sendSignInLinkToEmail(auth, email, actionCodeSettings)](./auth.md#sendsigninlinktoemail) | Sends a sign-in email link to the user with the specified email. |
4141
| [setPersistence(auth, persistence)](./auth.md#setpersistence) | Changes the type of persistence on the [Auth](./auth.auth.md#auth_interface) instance for the currently saved <code>Auth</code> session and applies this type of persistence for future sign-in requests, including sign-in with redirect requests. |
4242
| [signInAnonymously(auth)](./auth.md#signinanonymously) | Asynchronously signs in as an anonymous user. |
@@ -397,7 +397,7 @@ Promise&lt;[UserCredential](./auth.usercredential.md#usercredential_interface)<!
397397

398398
## fetchSignInMethodsForEmail()
399399

400-
Gets the list of possible sign in methods for the given email address.
400+
Gets the list of possible sign in methods for the given email address. This method returns an empty list when \[Email Enumeration Protection\](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled, irrespective of the number of authentication methods available for the given email.
401401

402402
This is useful to differentiate methods of sign-in for the same provider, eg. [EmailAuthProvider](./auth.emailauthprovider.md#emailauthprovider_class) which has 2 methods of sign-in, [SignInMethod](./auth.md#signinmethod)<!-- -->.EMAIL\_PASSWORD and [SignInMethod](./auth.md#signinmethod)<!-- -->.EMAIL\_LINK.
403403

@@ -412,7 +412,7 @@ export declare function fetchSignInMethodsForEmail(auth: Auth, email: string): P
412412
| Parameter | Type | Description |
413413
| --- | --- | --- |
414414
| auth | [Auth](./auth.auth.md#auth_interface) | The [Auth](./auth.auth.md#auth_interface) instance. |
415-
| email | string | The user's email address. |
415+
| email | string | The user's email address.<!-- -->Deprecated. Migrating off of this method is recommended as a security best-practice. Learn more in the Identity Platform documentation for \[Email Enumeration Protection\](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection). |
416416

417417
<b>Returns:</b>
418418

@@ -622,7 +622,7 @@ Promise&lt;void&gt;
622622

623623
## sendPasswordResetEmail()
624624

625-
Sends a password reset email to the given email address.
625+
Sends a password reset email to the given email address. This method does not throw an error when there's no user account with the given email address and \[Email Enumeration Protection\](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled.
626626

627627
To complete the password reset, call [confirmPasswordReset()](./auth.md#confirmpasswordreset) with the code supplied in the email sent to the user, along with the new password specified by the user.
628628

@@ -825,7 +825,7 @@ Promise&lt;[UserCredential](./auth.usercredential.md#usercredential_interface)<!
825825

826826
Asynchronously signs in using an email and password.
827827

828-
Fails with an error if the email address and password do not match.
828+
Fails with an error if the email address and password do not match. When \[Email Enumeration Protection\](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled, this method fails with "auth/invalid-credential" in case of an invalid email/password.
829829

830830
Note: The user's password is NOT the password used to access the user's email account. The email address serves as a unique identifier for the user, and the password is used to access the user's account in your Firebase project. See also: [createUserWithEmailAndPassword()](./auth.md#createuserwithemailandpassword)<!-- -->.
831831

@@ -1633,7 +1633,7 @@ export declare function updateEmail(user: User, newEmail: string): Promise<void>
16331633
| Parameter | Type | Description |
16341634
| --- | --- | --- |
16351635
| user | [User](./auth.user.md#user_interface) | The user. |
1636-
| newEmail | string | The new email address. |
1636+
| newEmail | string | The new email address.<!-- -->Throws "auth/operation-not-allowed" error when \[Email Enumeration Protection\](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled. Deprecated - Use [verifyBeforeUpdateEmail()](./auth.md#verifybeforeupdateemail) instead. |
16371637

16381638
<b>Returns:</b>
16391639

@@ -1851,6 +1851,7 @@ AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY: {
18511851
readonly INVALID_EMAIL: "auth/invalid-email";
18521852
readonly INVALID_EMULATOR_SCHEME: "auth/invalid-emulator-scheme";
18531853
readonly INVALID_IDP_RESPONSE: "auth/invalid-credential";
1854+
readonly INVALID_LOGIN_CREDENTIALS: "auth/invalid-credential";
18541855
readonly INVALID_MESSAGE_PAYLOAD: "auth/invalid-message-payload";
18551856
readonly INVALID_MFA_SESSION: "auth/invalid-multi-factor-session";
18561857
readonly INVALID_OAUTH_CLIENT_ID: "auth/invalid-oauth-client-id";

packages/auth/demo/src/index.js

+5
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,11 @@ function onAuthError(error) {
317317
if (error.code === 'auth/user-token-expired') {
318318
alertError('Token expired, please reauthenticate.');
319319
}
320+
if (error.code === 'auth/invalid-credential') {
321+
alertError(
322+
'Login credentials invalid. It is possible that the email/password combination does not exist.'
323+
);
324+
}
320325
}
321326
}
322327

packages/auth/src/api/authentication/idp.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describe('api/authentication/signInWithIdp', () => {
8383

8484
await expect(signInWithIdp(auth, request)).to.be.rejectedWith(
8585
FirebaseError,
86-
'Firebase: The supplied auth credential is malformed or has expired. (auth/invalid-credential).'
86+
'Firebase: The supplied auth credential is incorrect, malformed or has expired. (auth/invalid-credential).'
8787
);
8888
expect(mock.calls[0].request).to.eql(request);
8989
});

packages/auth/src/api/authentication/mfa.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe('api/authentication/startSignInPhoneMfa', () => {
8585

8686
await expect(startSignInPhoneMfa(auth, request)).to.be.rejectedWith(
8787
FirebaseError,
88-
'Firebase: The supplied auth credential is malformed or has expired. (auth/invalid-credential).'
88+
'Firebase: The supplied auth credential is incorrect, malformed or has expired. (auth/invalid-credential).'
8989
);
9090
expect(mock.calls[0].request).to.eql(request);
9191
});

packages/auth/src/api/authentication/token.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ describe('api/authentication/revokeToken', () => {
199199

200200
await expect(revokeToken(auth, request)).to.be.rejectedWith(
201201
FirebaseError,
202-
'Firebase: The supplied auth credential is malformed or has expired. (auth/invalid-credential).'
202+
'Firebase: The supplied auth credential is incorrect, malformed or has expired. (auth/invalid-credential).'
203203
);
204204
expect(mock.calls[0].request).to.eql(request);
205205
});

packages/auth/src/api/errors.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const enum ServerError {
4444
INVALID_ID_TOKEN = 'INVALID_ID_TOKEN',
4545
INVALID_IDP_RESPONSE = 'INVALID_IDP_RESPONSE',
4646
INVALID_IDENTIFIER = 'INVALID_IDENTIFIER',
47+
INVALID_LOGIN_CREDENTIALS = 'INVALID_LOGIN_CREDENTIALS',
4748
INVALID_MESSAGE_PAYLOAD = 'INVALID_MESSAGE_PAYLOAD',
4849
INVALID_MFA_PENDING_CREDENTIAL = 'INVALID_MFA_PENDING_CREDENTIAL',
4950
INVALID_OAUTH_CLIENT_ID = 'INVALID_OAUTH_CLIENT_ID',
@@ -144,14 +145,17 @@ export const SERVER_ERROR_MAP: Partial<ServerErrorMap<ServerError>> = {
144145
[ServerError.INVALID_PASSWORD]: AuthErrorCode.INVALID_PASSWORD,
145146
// This can only happen if the SDK sends a bad request.
146147
[ServerError.MISSING_PASSWORD]: AuthErrorCode.MISSING_PASSWORD,
148+
// Thrown if Email Enumeration Protection is enabled in the project and the email or password is
149+
// invalid.
150+
[ServerError.INVALID_LOGIN_CREDENTIALS]: AuthErrorCode.INVALID_CREDENTIAL,
147151

148152
// Sign up with email and password errors.
149153
[ServerError.EMAIL_EXISTS]: AuthErrorCode.EMAIL_EXISTS,
150154
[ServerError.PASSWORD_LOGIN_DISABLED]: AuthErrorCode.OPERATION_NOT_ALLOWED,
151155

152156
// Verify assertion for sign in with credential errors:
153-
[ServerError.INVALID_IDP_RESPONSE]: AuthErrorCode.INVALID_IDP_RESPONSE,
154-
[ServerError.INVALID_PENDING_TOKEN]: AuthErrorCode.INVALID_IDP_RESPONSE,
157+
[ServerError.INVALID_IDP_RESPONSE]: AuthErrorCode.INVALID_CREDENTIAL,
158+
[ServerError.INVALID_PENDING_TOKEN]: AuthErrorCode.INVALID_CREDENTIAL,
155159
[ServerError.FEDERATED_USER_ID_ALREADY_LINKED]:
156160
AuthErrorCode.CREDENTIAL_ALREADY_IN_USE,
157161

@@ -184,7 +188,7 @@ export const SERVER_ERROR_MAP: Partial<ServerErrorMap<ServerError>> = {
184188
// Phone Auth related errors.
185189
[ServerError.INVALID_CODE]: AuthErrorCode.INVALID_CODE,
186190
[ServerError.INVALID_SESSION_INFO]: AuthErrorCode.INVALID_SESSION_INFO,
187-
[ServerError.INVALID_TEMPORARY_PROOF]: AuthErrorCode.INVALID_IDP_RESPONSE,
191+
[ServerError.INVALID_TEMPORARY_PROOF]: AuthErrorCode.INVALID_CREDENTIAL,
188192
[ServerError.MISSING_SESSION_INFO]: AuthErrorCode.MISSING_SESSION_INFO,
189193
[ServerError.SESSION_EXPIRED]: AuthErrorCode.CODE_EXPIRED,
190194

packages/auth/src/core/errors.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export const enum AuthErrorCode {
6060
INVALID_DYNAMIC_LINK_DOMAIN = 'invalid-dynamic-link-domain',
6161
INVALID_EMAIL = 'invalid-email',
6262
INVALID_EMULATOR_SCHEME = 'invalid-emulator-scheme',
63-
INVALID_IDP_RESPONSE = 'invalid-credential',
63+
INVALID_CREDENTIAL = 'invalid-credential',
6464
INVALID_MESSAGE_PAYLOAD = 'invalid-message-payload',
6565
INVALID_MFA_SESSION = 'invalid-multi-factor-session',
6666
INVALID_OAUTH_CLIENT_ID = 'invalid-oauth-client-id',
@@ -217,8 +217,8 @@ function _debugErrorMap(): ErrorMap<AuthErrorCode> {
217217
'Your API key is invalid, please check you have copied it correctly.',
218218
[AuthErrorCode.INVALID_CERT_HASH]:
219219
'The SHA-1 certificate hash provided is invalid.',
220-
[AuthErrorCode.INVALID_IDP_RESPONSE]:
221-
'The supplied auth credential is malformed or has expired.',
220+
[AuthErrorCode.INVALID_CREDENTIAL]:
221+
'The supplied auth credential is incorrect, malformed or has expired.',
222222
[AuthErrorCode.INVALID_MESSAGE_PAYLOAD]:
223223
'The email template corresponding to this action contains invalid characters in its message. ' +
224224
'Please fix by going to the Auth email templates section in the Firebase Console.',
@@ -528,6 +528,7 @@ export const AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY = {
528528
INVALID_EMAIL: 'auth/invalid-email',
529529
INVALID_EMULATOR_SCHEME: 'auth/invalid-emulator-scheme',
530530
INVALID_IDP_RESPONSE: 'auth/invalid-credential',
531+
INVALID_LOGIN_CREDENTIALS: 'auth/invalid-credential',
531532
INVALID_MESSAGE_PAYLOAD: 'auth/invalid-message-payload',
532533
INVALID_MFA_SESSION: 'auth/invalid-multi-factor-session',
533534
INVALID_OAUTH_CLIENT_ID: 'auth/invalid-oauth-client-id',

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ import { _setActionCodeSettingsOnRequest } from './action_code_settings';
3333
import { getModularInstance } from '@firebase/util';
3434

3535
/**
36-
* Gets the list of possible sign in methods for the given email address.
36+
* Gets the list of possible sign in methods for the given email address. This method returns an
37+
* empty list when [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled, irrespective of the number of
38+
* authentication methods available for the given email.
3739
*
3840
* @remarks
3941
* This is useful to differentiate methods of sign-in for the same provider, eg.
@@ -44,6 +46,8 @@ import { getModularInstance } from '@firebase/util';
4446
* @param auth - The {@link Auth} instance.
4547
* @param email - The user's email address.
4648
*
49+
* Deprecated. Migrating off of this method is recommended as a security best-practice.
50+
* Learn more in the Identity Platform documentation for [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection).
4751
* @public
4852
*/
4953
export async function fetchSignInMethodsForEmail(

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ async function recachePasswordPolicy(auth: Auth): Promise<void> {
6161
}
6262

6363
/**
64-
* Sends a password reset email to the given email address.
64+
* Sends a password reset email to the given email address. This method does not throw an error when
65+
* there's no user account with the given email address and
66+
* [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled.
6567
*
6668
* @remarks
6769
* To complete the password reset, call {@link confirmPasswordReset} with the code supplied in
@@ -303,6 +305,8 @@ export async function createUserWithEmailAndPassword(
303305
*
304306
* @remarks
305307
* Fails with an error if the email address and password do not match.
308+
* When [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled,
309+
* this method fails with "auth/invalid-credential" in case of an invalid email/password.
306310
*
307311
* Note: The user's password is NOT the password used to access the user's email account. The
308312
* email address serves as a unique identifier for the user, and the password is used to access

packages/auth/src/core/user/account_info.ts

+3
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ export async function updateProfile(
8888
* @param user - The user.
8989
* @param newEmail - The new email address.
9090
*
91+
* Throws "auth/operation-not-allowed" error when [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled.
92+
* Deprecated - Use {@link verifyBeforeUpdateEmail} instead.
93+
*
9194
* @public
9295
*/
9396
export function updateEmail(user: User, newEmail: string): Promise<void> {

0 commit comments

Comments
 (0)