Avatar

Matteus
Hemström

GithubLinkedInStackOverflow

The Credential Management API

Posted April 13, 2016

Web browsers have for a long time tried to manage user credentials by providing a “save password” and autofill feature. This is implemented by making assumptions on the web application. The web browser may for example guess that a form with a password field should be saved. The assumptions work pretty well and most times the browser makes the right call. In some cases web applications does not use a form but a XMLHttpRequest to send the login request. In that case, the browser also has to figure out which XMLHttpRequests are login requests. More problems arise with the autofill feature, the web application may have to figure out on its own when the browser autofill an input element. It is also problematic to cope with the styling that a browser may apply to autofilled elements. Non-standard css rules can be applied to Chrome, such as :webkit-autofill. The point is, there is a gap between browsers and web applications when it comes to credential management. W3C address this issue with a draft called “Credential Management Level 1”.

What it looks like

demo.gif

Note that the credentials are never stored in the form and there is no need for an autofill. Instead of letting the browser make guesses about which credentials to store, the web application is in charge and communicates to the browser when to load and store any credentials.

The code

The credentials API resides in navigator.credentials. It currently expose three functions:

  • navigator.credentials.get(options)
  • navigator.credentials.store(credential)
  • navigator.credentials.requireUserMediation()

The authentication workflow with stored credentials in its simplest form is as follows:

That is:

  1. Get the credentials.
  2. Perform authentication by sending the credentials to the server.

Two types of credentials can be requested: password credentials and federated credentials. The most basic type is password credentials which is a username/password pair. Password credentials are requested by specifying { password: true }. Federated credentials are used to support authentication from an external provider, think of it as support for “Login with Google” or another provider. I have not experimented with federated credentials and will only discuss password credentials.

When a user visit your website for the first time there are no credentials stored. The user therefore need to authenticate using a login form. The credential management API can be used to store the credentials from the login form:

When navigator.credentials.store(credentials) is executed the browser will display a window giving the user an option to store the credentials. If the user accepts, the credentials will be stored and available with navigator.credentials.get.

The draft also specifies that navigator.credentials.get can take an option called unmediated. It is supposed to suppress any UI to be displayed by the browser. This could be used by the web application to query the browser for any autologin credentials.

The function requireUserMediation is a bit trickier, and honestly I do not understand it fully. My problem with requireUserMediation is that it seems to do multiple things, which makes it hard to use. When invoking requireUserMediation Chrome will display a popup that lets the user choose which account to login with. It will also turn off automatic login. I used it in combination with navigator.credentials.get to force the display of the select credentials window:

Current status and problems

I worked with the editor draft released 31 March 2016 and with Chrome 52 (using canary channel). The credential management API was enabled by default with Chrome 51 but this will probably be reverted and disabled by default in next releases to come. This seems like the right call based on my work with it. The API is just not ready yet, and the current implementation in Chrome is based on a specification that is a few months old, and the specification is still changing a lot!

The Chrome 52 implementation does not support the unmediated option and I could not get the old suppressUI flag to work either. There are also inconsistencies with credentials support for window.fetch, I found that Chrome does not allow credentials to be passed to fetch as a option, instead the credentials must be passed as the body:

I also have some concerns about requireUserMediation. I feel like it is not clear what the function does. From the function name I can conclude that the user will get the opportunity to make some choice, but it is not clear to me what that choice is. The function also has the side effect to turn off automatic login. The draft says that it ”[…] might be called after a user signs out of a website, for instance, in order to ensure that she isn’t automatically signed back in next time she visits.” But then why does it also display a window that allows the user to select credentials?

I think there are some parts missing from this API. There is no way of communicating to the browser that a login request failed. With the Chrome 52 implementation this results in a inconsistent UI. When we invoke navigator.credentials.get the browser will notify the user with a “Logging in as {username}”, even though we have not sent the login request yet! And the web developer have no way of display “Login successful” or “Login failed” with the same browser UI.

Conclusion

The specification is still in active development and not yet stable. It is unclear to me why the Chrome developers would enable their implementation by default this early. But the API is interesting and it shows promise because browsers currently lack a smooth and elegant way to assist the user with authentication.