Skip to content

Commit c9e2b0b

Browse files
authored
Add support for validating passwords against the password policy in auth (#7514)
* Implement validatePassword endpoint for public API with PasswordPolicy and PasswordValidationStatus public types * Update auth demo to include a section for password validation
1 parent 0038e11 commit c9e2b0b

30 files changed

+2864
-105
lines changed

.changeset/thick-lions-divide.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/auth': minor
3+
'firebase': minor
4+
---
5+
6+
Add a validatePassword method for validating passwords against the password policy configured for the project or a tenant. This method returns a status object that can be used to display the requirements of the password policy and whether each one was met.

common/api-review/auth.api.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,33 @@ export interface ParsedToken {
563563
'sub'?: string;
564564
}
565565

566+
// @public
567+
export interface PasswordPolicy {
568+
readonly allowedNonAlphanumericCharacters: string;
569+
readonly customStrengthOptions: {
570+
readonly minPasswordLength?: number;
571+
readonly maxPasswordLength?: number;
572+
readonly containsLowercaseLetter?: boolean;
573+
readonly containsUppercaseLetter?: boolean;
574+
readonly containsNumericCharacter?: boolean;
575+
readonly containsNonAlphanumericCharacter?: boolean;
576+
};
577+
readonly enforcementState: string;
578+
readonly forceUpgradeOnSignin: boolean;
579+
}
580+
581+
// @public
582+
export interface PasswordValidationStatus {
583+
readonly containsLowercaseLetter?: boolean;
584+
readonly containsNonAlphanumericCharacter?: boolean;
585+
readonly containsNumericCharacter?: boolean;
586+
readonly containsUppercaseLetter?: boolean;
587+
readonly isValid: boolean;
588+
readonly meetsMaxPasswordLength?: boolean;
589+
readonly meetsMinPasswordLength?: boolean;
590+
readonly passwordPolicy: PasswordPolicy;
591+
}
592+
566593
// @public
567594
export interface Persistence {
568595
readonly type: 'SESSION' | 'LOCAL' | 'NONE';
@@ -869,6 +896,9 @@ export interface UserMetadata {
869896
// @public
870897
export type UserProfile = Record<string, unknown>;
871898

899+
// @public
900+
export function validatePassword(auth: Auth, password: string): Promise<PasswordValidationStatus>;
901+
872902
// @public
873903
export function verifyBeforeUpdateEmail(user: User, newEmail: string, actionCodeSettings?: ActionCodeSettings | null): Promise<void>;
874904

docs-devsite/auth.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Firebase Authentication
4949
| [signOut(auth)](./auth.md#signout) | Signs out the current user. |
5050
| [updateCurrentUser(auth, user)](./auth.md#updatecurrentuser) | Asynchronously sets the provided user as [Auth.currentUser](./auth.auth.md#authcurrentuser) on the [Auth](./auth.auth.md#auth_interface) instance. |
5151
| [useDeviceLanguage(auth)](./auth.md#usedevicelanguage) | Sets the current language to the default device/browser preference. |
52+
| [validatePassword(auth, password)](./auth.md#validatepassword) | Validates the password against the password policy configured for the project or tenant. |
5253
| [verifyPasswordResetCode(auth, code)](./auth.md#verifypasswordresetcode) | Checks a password reset code sent to the user by email or other out-of-band mechanism. |
5354
| <b>function(link...)</b> |
5455
| [parseActionCodeURL(link)](./auth.md#parseactioncodeurl) | Parses the email action link string and returns an [ActionCodeURL](./auth.actioncodeurl.md#actioncodeurl_class) if the link is valid, otherwise returns null. |
@@ -124,6 +125,8 @@ Firebase Authentication
124125
| [MultiFactorUser](./auth.multifactoruser.md#multifactoruser_interface) | An interface that defines the multi-factor related properties and operations pertaining to a [User](./auth.user.md#user_interface)<!-- -->. |
125126
| [OAuthCredentialOptions](./auth.oauthcredentialoptions.md#oauthcredentialoptions_interface) | Defines the options for initializing an [OAuthCredential](./auth.oauthcredential.md#oauthcredential_class)<!-- -->. |
126127
| [ParsedToken](./auth.parsedtoken.md#parsedtoken_interface) | Interface representing a parsed ID token. |
128+
| [PasswordPolicy](./auth.passwordpolicy.md#passwordpolicy_interface) | A structure specifying password policy requirements. |
129+
| [PasswordValidationStatus](./auth.passwordvalidationstatus.md#passwordvalidationstatus_interface) | A structure indicating which password policy requirements were met or violated and what the requirements are. |
127130
| [Persistence](./auth.persistence.md#persistence_interface) | An interface covering the possible persistence mechanism types. |
128131
| [PhoneMultiFactorAssertion](./auth.phonemultifactorassertion.md#phonemultifactorassertion_interface) | The class for asserting ownership of a phone second factor. Provided by [PhoneMultiFactorGenerator.assertion()](./auth.phonemultifactorgenerator.md#phonemultifactorgeneratorassertion)<!-- -->. |
129132
| [PhoneMultiFactorEnrollInfoOptions](./auth.phonemultifactorenrollinfooptions.md#phonemultifactorenrollinfooptions_interface) | Options used for enrolling a second factor. |
@@ -1077,6 +1080,39 @@ export declare function useDeviceLanguage(auth: Auth): void;
10771080

10781081
void
10791082

1083+
## validatePassword()
1084+
1085+
Validates the password against the password policy configured for the project or tenant.
1086+
1087+
If no tenant ID is set on the `Auth` instance, then this method will use the password policy configured for the project. Otherwise, this method will use the policy configured for the tenant. If a password policy has not been configured, then the default policy configured for all projects will be used.
1088+
1089+
If an auth flow fails because a submitted password does not meet the password policy requirements and this method has previously been called, then this method will use the most recent policy available when called again.
1090+
1091+
<b>Signature:</b>
1092+
1093+
```typescript
1094+
export declare function validatePassword(auth: Auth, password: string): Promise<PasswordValidationStatus>;
1095+
```
1096+
1097+
### Parameters
1098+
1099+
| Parameter | Type | Description |
1100+
| --- | --- | --- |
1101+
| auth | [Auth](./auth.auth.md#auth_interface) | The [Auth](./auth.auth.md#auth_interface) instance. |
1102+
| password | string | The password to validate. |
1103+
1104+
<b>Returns:</b>
1105+
1106+
Promise&lt;[PasswordValidationStatus](./auth.passwordvalidationstatus.md#passwordvalidationstatus_interface)<!-- -->&gt;
1107+
1108+
### Example
1109+
1110+
1111+
```javascript
1112+
validatePassword(auth, 'some-password');
1113+
1114+
```
1115+
10801116
## verifyPasswordResetCode()
10811117

10821118
Checks a password reset code sent to the user by email or other out-of-band mechanism.

docs-devsite/auth.passwordpolicy.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
Project: /docs/reference/js/_project.yaml
2+
Book: /docs/reference/_book.yaml
3+
page_type: reference
4+
5+
{% comment %}
6+
DO NOT EDIT THIS FILE!
7+
This is generated by the JS SDK team, and any local changes will be
8+
overwritten. Changes should be made in the source code at
9+
https://github.com/firebase/firebase-js-sdk
10+
{% endcomment %}
11+
12+
# PasswordPolicy interface
13+
A structure specifying password policy requirements.
14+
15+
<b>Signature:</b>
16+
17+
```typescript
18+
export interface PasswordPolicy
19+
```
20+
21+
## Properties
22+
23+
| Property | Type | Description |
24+
| --- | --- | --- |
25+
| [allowedNonAlphanumericCharacters](./auth.passwordpolicy.md#passwordpolicyallowednonalphanumericcharacters) | string | List of characters that are considered non-alphanumeric during validation. |
26+
| [customStrengthOptions](./auth.passwordpolicy.md#passwordpolicycustomstrengthoptions) | { readonly minPasswordLength?: number; readonly maxPasswordLength?: number; readonly containsLowercaseLetter?: boolean; readonly containsUppercaseLetter?: boolean; readonly containsNumericCharacter?: boolean; readonly containsNonAlphanumericCharacter?: boolean; } | Requirements enforced by this password policy. |
27+
| [enforcementState](./auth.passwordpolicy.md#passwordpolicyenforcementstate) | string | The enforcement state of the policy. Can be 'OFF' or 'ENFORCE'. |
28+
| [forceUpgradeOnSignin](./auth.passwordpolicy.md#passwordpolicyforceupgradeonsignin) | boolean | Whether existing passwords must meet the policy. |
29+
30+
## PasswordPolicy.allowedNonAlphanumericCharacters
31+
32+
List of characters that are considered non-alphanumeric during validation.
33+
34+
<b>Signature:</b>
35+
36+
```typescript
37+
readonly allowedNonAlphanumericCharacters: string;
38+
```
39+
40+
## PasswordPolicy.customStrengthOptions
41+
42+
Requirements enforced by this password policy.
43+
44+
<b>Signature:</b>
45+
46+
```typescript
47+
readonly customStrengthOptions: {
48+
readonly minPasswordLength?: number;
49+
readonly maxPasswordLength?: number;
50+
readonly containsLowercaseLetter?: boolean;
51+
readonly containsUppercaseLetter?: boolean;
52+
readonly containsNumericCharacter?: boolean;
53+
readonly containsNonAlphanumericCharacter?: boolean;
54+
};
55+
```
56+
57+
## PasswordPolicy.enforcementState
58+
59+
The enforcement state of the policy. Can be 'OFF' or 'ENFORCE'.
60+
61+
<b>Signature:</b>
62+
63+
```typescript
64+
readonly enforcementState: string;
65+
```
66+
67+
## PasswordPolicy.forceUpgradeOnSignin
68+
69+
Whether existing passwords must meet the policy.
70+
71+
<b>Signature:</b>
72+
73+
```typescript
74+
readonly forceUpgradeOnSignin: boolean;
75+
```
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
Project: /docs/reference/js/_project.yaml
2+
Book: /docs/reference/_book.yaml
3+
page_type: reference
4+
5+
{% comment %}
6+
DO NOT EDIT THIS FILE!
7+
This is generated by the JS SDK team, and any local changes will be
8+
overwritten. Changes should be made in the source code at
9+
https://github.com/firebase/firebase-js-sdk
10+
{% endcomment %}
11+
12+
# PasswordValidationStatus interface
13+
A structure indicating which password policy requirements were met or violated and what the requirements are.
14+
15+
<b>Signature:</b>
16+
17+
```typescript
18+
export interface PasswordValidationStatus
19+
```
20+
21+
## Properties
22+
23+
| Property | Type | Description |
24+
| --- | --- | --- |
25+
| [containsLowercaseLetter](./auth.passwordvalidationstatus.md#passwordvalidationstatuscontainslowercaseletter) | boolean | Whether the password contains a lowercase letter, or undefined if not required. |
26+
| [containsNonAlphanumericCharacter](./auth.passwordvalidationstatus.md#passwordvalidationstatuscontainsnonalphanumericcharacter) | boolean | Whether the password contains a non-alphanumeric character, or undefined if not required. |
27+
| [containsNumericCharacter](./auth.passwordvalidationstatus.md#passwordvalidationstatuscontainsnumericcharacter) | boolean | Whether the password contains a numeric character, or undefined if not required. |
28+
| [containsUppercaseLetter](./auth.passwordvalidationstatus.md#passwordvalidationstatuscontainsuppercaseletter) | boolean | Whether the password contains an uppercase letter, or undefined if not required. |
29+
| [isValid](./auth.passwordvalidationstatus.md#passwordvalidationstatusisvalid) | boolean | Whether the password meets all requirements. |
30+
| [meetsMaxPasswordLength](./auth.passwordvalidationstatus.md#passwordvalidationstatusmeetsmaxpasswordlength) | boolean | Whether the password meets the maximum password length, or undefined if not required. |
31+
| [meetsMinPasswordLength](./auth.passwordvalidationstatus.md#passwordvalidationstatusmeetsminpasswordlength) | boolean | Whether the password meets the minimum password length, or undefined if not required. |
32+
| [passwordPolicy](./auth.passwordvalidationstatus.md#passwordvalidationstatuspasswordpolicy) | [PasswordPolicy](./auth.passwordpolicy.md#passwordpolicy_interface) | The policy used to validate the password. |
33+
34+
## PasswordValidationStatus.containsLowercaseLetter
35+
36+
Whether the password contains a lowercase letter, or undefined if not required.
37+
38+
<b>Signature:</b>
39+
40+
```typescript
41+
readonly containsLowercaseLetter?: boolean;
42+
```
43+
44+
## PasswordValidationStatus.containsNonAlphanumericCharacter
45+
46+
Whether the password contains a non-alphanumeric character, or undefined if not required.
47+
48+
<b>Signature:</b>
49+
50+
```typescript
51+
readonly containsNonAlphanumericCharacter?: boolean;
52+
```
53+
54+
## PasswordValidationStatus.containsNumericCharacter
55+
56+
Whether the password contains a numeric character, or undefined if not required.
57+
58+
<b>Signature:</b>
59+
60+
```typescript
61+
readonly containsNumericCharacter?: boolean;
62+
```
63+
64+
## PasswordValidationStatus.containsUppercaseLetter
65+
66+
Whether the password contains an uppercase letter, or undefined if not required.
67+
68+
<b>Signature:</b>
69+
70+
```typescript
71+
readonly containsUppercaseLetter?: boolean;
72+
```
73+
74+
## PasswordValidationStatus.isValid
75+
76+
Whether the password meets all requirements.
77+
78+
<b>Signature:</b>
79+
80+
```typescript
81+
readonly isValid: boolean;
82+
```
83+
84+
## PasswordValidationStatus.meetsMaxPasswordLength
85+
86+
Whether the password meets the maximum password length, or undefined if not required.
87+
88+
<b>Signature:</b>
89+
90+
```typescript
91+
readonly meetsMaxPasswordLength?: boolean;
92+
```
93+
94+
## PasswordValidationStatus.meetsMinPasswordLength
95+
96+
Whether the password meets the minimum password length, or undefined if not required.
97+
98+
<b>Signature:</b>
99+
100+
```typescript
101+
readonly meetsMinPasswordLength?: boolean;
102+
```
103+
104+
## PasswordValidationStatus.passwordPolicy
105+
106+
The policy used to validate the password.
107+
108+
<b>Signature:</b>
109+
110+
```typescript
111+
readonly passwordPolicy: PasswordPolicy;
112+
```

packages/auth/README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ firebase emulators:exec --project foo-bar --only auth "yarn test:integration:loc
5454

5555
### Integration testing with the production backend
5656

57-
Currently, MFA TOTP tests only run against the production backend (since they are not supported on the emulator yet).
57+
Currently, MFA TOTP and password policy tests only run against the production backend (since they are not supported on the emulator yet).
5858
Running against the backend also makes it a more reliable end-to-end test.
5959

6060
The TOTP tests require the following email/password combination to exist in the project, so if you are running this test against your test project, please create this user:
@@ -71,6 +71,33 @@ curl -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Conten
7171
}'
7272
```
7373

74+
The password policy tests require a tenant configured with a password policy that requires all options to exist in the project.
75+
76+
If you are running this test against your test project, please create the tenant and configure the policy with the following curl command:
77+
78+
```
79+
curl -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json" -H "X-Goog-User-Project: ${PROJECT_ID}" -X POST https://identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/tenants -d '{
80+
"displayName": "passpol-tenant",
81+
"passwordPolicyConfig": {
82+
"passwordPolicyEnforcementState": "ENFORCE",
83+
"passwordPolicyVersions": [
84+
{
85+
"customStrengthOptions": {
86+
"minPasswordLength": 8,
87+
"maxPasswordLength": 24,
88+
"containsLowercaseCharacter": true,
89+
"containsUppercaseCharacter": true,
90+
"containsNumericCharacter": true,
91+
"containsNonAlphanumericCharacter": true
92+
}
93+
}
94+
]
95+
}
96+
}'
97+
```
98+
99+
Replace the tenant ID `passpol-tenant-d7hha` in [test/integration/flows/password_policy.test.ts](https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/test/integration/flows/password_policy.test.ts) with the ID for the newly created tenant. The tenant ID can be found at the end of the `name` property in the response and is in the format `passpol-tenant-xxxxx`.
100+
74101
### Selenium Webdriver tests
75102

76103
These tests assume that you have both Firefox and Chrome installed on your

0 commit comments

Comments
 (0)