Password Credentials

Unofficial Proposal Draft,

More details about this document
This version:
https://w3c.github.io/password-credentials/
Version History:
https://github.com/w3c/webappsec-credential-management/commits/main/index.src.html
Feedback:
public-webappsec@w3.org with subject line “[password-credentials] … message topic …” (archives)
Issue Tracking:
GitHub
Participate:
File an issue (open issues)

Abstract

This specification describes a Password Credential type that allows websites to request a user’s username and password and optionally store them for future use.

Status of this document

1. Password Credentials

For good or for ill, many websites rely on username/password pairs as an authentication mechanism. The PasswordCredential interface is a credential meant to enable this use case, storing both a username and password, as well as metadata that can help a user choose the right account from within a credential chooser.

1.1. Examples

1.1.1. Password-based Sign-in

MegaCorp, Inc. supports passwords, and can use navigator.credentials.get() to obtain username/password pairs from a user’s credential store:
navigator.credentials
  .get({ 'password': true })
  .then(credential => {
    if (!credential) {
      // The user either doesn’t have credentials for this site, or
      // refused to share them. Insert some code here to fall back to
      // a basic login form.
      return;
    }
    if (credential.type == 'password') {
      var form = new FormData();
      form.append('username_field', credential.id);
      form.append('password_field', credential.password);
      var opt = {
        method: 'POST',
        body: form,
        credentials: 'include'  // Send cookies.
      };
      fetch('https://example.com/loginEndpoint', opt)
        .then(function (response) {
          if (/* |response| indicates a successful login */) {
            // Record that the credential was effective. See note below.
            navigator.credentials.store(credential);
            // Notify the user that sign-in succeeded! Do amazing, signed-in things!
            // Maybe navigate to a landing page via location.href =
            // '/signed-in-experience'?
          } else {
            // Insert some code here to fall back to a basic login form.
          }
        });
    }
  });

Alternatively, the website could just copy the credential data into a form and call submit() on the form:

navigator.credentials
  .get({ 'password': true })
  .then(credential => {
    if (!credential) {
      return; // as above...
    }
    if (credential.type === 'password') {
      document.querySelector('input[name=username_field]').value =
        credential.id;
      document.querySelector('input[name=password_field]').value =
        credential.password;
      document.getElementById('myform').submit();
    }
  });

Note that the former method is much preferred, as it contains an explicit call to store() and saves the credentials. The form based mechanism relies on form submission, which navigates the browsing context, making it difficult to ensure that store() is called after successful sign-in.

Note: The credential chooser presented by the user agent could allow the user to choose credentials that aren’t actually stored for the current origin. For instance, it might offer up credentials from https://m.example.com when signing into https://www.example.com (as described in Credential Management 1 § 6.1 Cross-domain credential access), or it might allow a user to create a new credential on the fly. Developers can deal gracefully with this uncertainty by calling store() every time credentials are successfully used, even right after credentials have been retrieved from get(): if the credentials aren’t yet stored for the origin, the user will be given the opportunity to do so. If they are stored, the user won’t be prompted.

1.1.2. Post-sign-in Confirmation

To ensure that users are offered to store new credentials after a successful sign-in, they can to be passed to store().

If a user is signed in by submitting the credentials to a sign-in endpoint via fetch(), we can check the response to determine whether the user was signed in successfully, and notify the user agent accordingly. Given a sign-in form like the following:
<form action="https://example.com/login" method="POST" id="theForm">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" autocomplete="username">
  <label for="password">Password</label>
  <input type="password" id="password" name="password" autocomplete="current-password">
  <input type="submit">
</form>

Then the developer can handle the form submission with something like the following handler:

document.querySelector('#theForm').addEventListener('submit', e => {
    if (window.PasswordCredential) {
      e.preventDefault();

      // Construct a new PasswordCredential from the HTMLFormElement
      // that fired the "submit" event: this will suck up the values of the fields
      // labeled with "username" and "current-password" autocomplete
      // attributes:
      var c = new PasswordCredential(e.target);

      // Fetch the form’s action URL, passing that new credential object in
      // as a FormData object. If the response indicates success, tell the user agent
      // so it can ask the user to store the password for future use:
      var opt = {
        method: 'POST',
        body: new FormData(e.target),
        credentials: 'include'  // Send cookies.
      };
      fetch(e.target.action, opt).then(r => {
        if (/* |r| is a "successful" Response */)
          navigator.credentials.store(c);
      });
    }
});

1.1.3. Change Password

This same storage mechanism can be reused for "password change" with no modifications: if the user changes their credentials, the website can notify the user agent that they’ve successfully signed in with new credentials. The user agent can then update the credentials it stores:

MegaCorp Inc. allows users to change their passwords by POSTing data to a backend server asynchronously. After doing so successfully, they can update the user’s credentials by calling store() with the new information.

Given a password change form like the following:

<form action="https://example.com/changePassword" method="POST" id="theForm">
  <input type="hidden" name="username" autocomplete="username" value="user">
  <label for="password">New Password</label>
  <input type="password" id="password" name="password" autocomplete="new-password">
  <input type="submit">
</form>

The developer can handle the form submission with something like the following:

document.querySelector('#theForm').addEventListener('submit', e => {
  if (window.PasswordCredential) {
    e.preventDefault();

    // Construct a new PasswordCredential from the HTMLFormElement
    // that fired the "submit" event: this will suck up the values of the fields
    // labeled with "username" and "new-password" autocomplete
    // attributes:
    var c = new PasswordCredential(e.target);

    // Fetch the form’s action URL, passing that new credential object in
    // as a FormData object. If the response indicates success, tell the user agent
    // so it can ask the user to store the password for future use:
    var opt = {
      method: 'POST',
      body: new FormData(e.target),
      credentials: 'include'  // Send cookies.
    };
    fetch(e.target.action, opt).then(r => {
      if (/* |r| is a "successful" Response */)
        navigator.credentials.store(c);
    });
  }
});

1.2. The PasswordCredential Interface

[Exposed=Window,
 SecureContext]
interface PasswordCredential : Credential {
  constructor(HTMLFormElement form);
  constructor(PasswordCredentialData data);
  readonly attribute USVString password;
};
PasswordCredential includes CredentialUserData;

partial dictionary CredentialRequestOptions {
  boolean password = false;
};
password, of type USVString, readonly

This attribute represents the password of the credential.

[[type]]

The PasswordCredential interface object has an internal slot named [[type]] whose value is "password".

[[discovery]]

The PasswordCredential interface object has an internal slot named [[discovery]] whose value is "credential store".

PasswordCredential(form)

This constructor accepts an HTMLFormElement (form), and runs the following steps:

  1. Let origin be the current settings object's origin.

  2. Let r be the result of executing Create a PasswordCredential from an HTMLFormElement given form and origin.

  3. If r is an exception, throw r.

    Otherwise, return r.

PasswordCredential(data)

This constructor accepts a PasswordCredentialData (data), and runs the following steps:

  1. Let r be the result of executing Create a PasswordCredential from PasswordCredentialData on data.

  2. If r is an exception, throw r.

    Otherwise, return r.

PasswordCredential objects can be created via navigator.credentials.create() either explicitly by passing in a PasswordCredentialData dictionary, or based on the contents of an HTMLFormElement's submittable elements.

dictionary PasswordCredentialData : CredentialData {
  USVString name;
  USVString iconURL;
  required USVString origin;
  required USVString password;
};

typedef (PasswordCredentialData or HTMLFormElement) PasswordCredentialInit;

partial dictionary CredentialCreationOptions {
  PasswordCredentialInit password;
};

PasswordCredential objects are origin bound.

PasswordCredential's interface object inherits Credential's implementation of [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors), and defines its own implementation of [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors), [[Create]](origin, options, sameOriginWithAncestors), and [[Store]](credential, sameOriginWithAncestors).

1.3. Algorithms

1.3.1. PasswordCredential’s [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)

[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors) is called with an origin (origin), a CredentialRequestOptions (options), and a boolean which is true if and only if the calling context is same-origin with its ancestors (sameOriginWithAncestors). The algorithm returns a set of Credential objects from the credential store. If no matching Credential objects are available, the returned set will be empty.

The algorithm will throw a NotAllowedError if sameOriginWithAncestors is not true.

  1. Assert: options["password"] exists.

  2. If sameOriginWithAncestors is false, throw a "NotAllowedError" DOMException.

    Note: This restriction aims to address the concern raised in Credential Management 1 § 6.4 Origin Confusion.

  3. Return the empty set if options["password"] is not true.

  4. Return the result of retrieving credentials from the credential store that match the following filter:

    1. The credential is a PasswordCredential

    2. The credential’s [[origin]] is the same origin as origin.

1.3.2. PasswordCredential’s [[Create]](origin, options, sameOriginWithAncestors)

[[Create]](origin, options, sameOriginWithAncestors) is called with an origin (origin), a CredentialCreationOptions (options), and a boolean which is true if and only if the calling context is same-origin with its ancestors (sameOriginWithAncestors). The algorithm returns a PasswordCredential if one can be created, null otherwise. The CredentialCreationOptions dictionary must have a password member which holds either an HTMLFormElement or a PasswordCredentialData. If that member’s value cannot be used to create a PasswordCredential, this algorithm will throw a TypeError exception.

  1. Assert: options["password"] exists, and sameOriginWithAncestors is unused.

  2. If options["password"] is an HTMLFormElement, return the result of executing Create a PasswordCredential from an HTMLFormElement given options["password"] and origin. Rethrow any exceptions.

  3. If options["password"] is a PasswordCredentialData, return the result of executing Create a PasswordCredential from PasswordCredentialData given options["password"]. Rethrow any exceptions.

  4. Throw a TypeError exception.

1.3.3. PasswordCredential’s [[Store]](credential, sameOriginWithAncestors)

[[Store]](credential, sameOriginWithAncestors) is called with a PasswordCredential (credential), and a boolean which is true if and only if the calling context is same-origin with its ancestors (sameOriginWithAncestors). The algorithm returns undefined once credential is persisted to the credential store.

The algorithm will return a NotAllowedError if sameOriginWithAncestors is not true.

  1. Throw a "NotAllowedError" DOMException without altering the user agent’s credential store if sameOriginWithAncestors is false.

    Note: This restriction aims to address the concern raised in Credential Management 1 § 6.4 Origin Confusion.

  2. If the user agent’s credential store contains a PasswordCredential (stored) whose id attribute is credential’s id and whose [[origin]] slot is the same origin as credential’s [[origin]], then:

    1. If the user grants permission to update credentials (as discussed when defining user mediation), then:

      1. Set stored’s password to credential’s password.

      2. Set stored’s name to credential’s name.

      3. Set stored’s iconURL to credential’s iconURL.

    Otherwise, if the user grants permission to store credentials (as discussed when defining user mediation, then:

    1. Store a PasswordCredential in the credential store with the following properties:

      id

      credential’s id

      name,

      credential’s name

      iconURL

      credential’s iconURL

      [[origin]]

      credential’s [[origin]]

      password

      credential’s password

1.3.4. Create a PasswordCredential from an HTMLFormElement

To Create a PasswordCredential from an HTMLFormElement, given an HTMLFormElement (form) and an origin (origin), run these steps.

Note: § 1.1.2 Post-sign-in Confirmation and § 1.1.3 Change Password provide examples of the intended usage.

  1. Let data be a new PasswordCredentialData dictionary.

  2. Set data’s origin member’s value to origin’s value.

  3. Let formData be the result of executing the FormData constructor on form.

  4. Let elements be a list of all the submittable elements whose form owner is form, in tree order.

  5. Let newPasswordObserved be false.

  6. For each field in elements, run the following steps:

    1. If field does not have an autocomplete attribute, then skip to the next field.

    2. Let name be the value of field’s name attribute.

    3. If formData’s has() method returns false when executed on name, then skip to the next field.

    4. If field’s autocomplete attribute’s value contains one or more autofill detail tokens (tokens), then:

      1. For each token in tokens:

        1. If token is an ASCII case-insensitive match for one of the following strings, run the associated steps:

          "new-password"

          Set data’s password member’s value to the result of executing formData’s get() method on name, and newPasswordObserved to true.

          "current-password"

          If newPasswordObserved is false, set data’s password member’s value to the result of executing formData’s get() method on name.

          Note: By checking that newPasswordObserved is false, new-password fields take precedence over current-password fields.

          "photo"

          Set data’s iconURL member’s value to the result of executing formData’s get() method on name.

          "name"
          "nickname"

          Set data’s name member’s value to the result of executing formData’s get() method on name.

          "username"

          Set data’s id member’s value to the result of executing formData’s get() method on name.

  7. Let c be the result of executing Create a PasswordCredential from PasswordCredentialData on data. If that threw an exception, rethrow that exception.

  8. Assert: c is a PasswordCredential.

  9. Return c.

1.3.5. Create a PasswordCredential from PasswordCredentialData

To Create a PasswordCredential from PasswordCredentialData, given an PasswordCredentialData (data), run these steps.

  1. Let c be a new PasswordCredential object.

  2. If any of the following are the empty string, throw a TypeError exception:

    • data’s id member’s value

    • data’s origin member’s value

    • data’s password member’s value

  3. Set c’s properties as follows:

    password

    data’s password member’s value

    id

    data’s id member’s value

    iconURL

    data’s iconURL member’s value

    name

    data’s name member’s value

    [[origin]]

    data’s origin member’s value.

  4. Return c.

1.3.6. CredentialRequestOptions Matching for PasswordCredential

Given a CredentialRequestOptions (options), the following algorithm returns "Matches" if the PasswordCredential should be available as a response to a get() request, and "Does Not Match" otherwise.

  1. If options has a password member whose value is true, then return "Matches".

  2. Return "Does Not Match".

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CREDENTIAL-MANAGEMENT-1]
Nina Satragno; Marcos Caceres. Credential Management Level 1. URL: https://w3c.github.io/webappsec-credential-management/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/
[XHR]
Anne van Kesteren. XMLHttpRequest Standard. Living Standard. URL: https://xhr.spec.whatwg.org/

Informative References

[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/

IDL Index

[Exposed=Window,
 SecureContext]
interface PasswordCredential : Credential {
  constructor(HTMLFormElement form);
  constructor(PasswordCredentialData data);
  readonly attribute USVString password;
};
PasswordCredential includes CredentialUserData;

partial dictionary CredentialRequestOptions {
  boolean password = false;
};

dictionary PasswordCredentialData : CredentialData {
  USVString name;
  USVString iconURL;
  required USVString origin;
  required USVString password;
};

typedef (PasswordCredentialData or HTMLFormElement) PasswordCredentialInit;

partial dictionary CredentialCreationOptions {
  PasswordCredentialInit password;
};