The Credential Management API is a browser API that allows a JavaScript application to interact with the integrated password manager.
With the Credential Management API, an application can store and retrieve passwords and federated login credentials and plays a role in the WebAuthn standard, where it stores public keys.
This blog post focuses on the password part of the interface and looks at how the API can support the traditional username/password login flow.
The following example is going to use the PasswordCredential object and calls
the store()
and get()
methods to store password credentials into the password manager and to retrieve them.
This part of the Credential Management API is only implemented in Chrome at the time of writing (June 2019). Check out caniuse.com website for the current support: https://caniuse.com/#feat=credential-management
Demo application ¶
This blog post shows you an example with a Spring Boot back end and an Ionic front end. The Spring Boot application uses Spring Security to implement a traditional session cookie-based form login process. I won't describe the back end further because the Credential Management API is a client-side API and works with any back-end technology.
You find the source code for the Spring Boot application on GitHub:
https://github.com/ralscha/blog2019/tree/master/credential/server
The Ionic application consists of a home page that users only see when they are logged in and a login page. The home page is protected with a CanActivate Angular route guard that checks if a user is logged in, if not, redirects to the login page.
Here is the route configuration of the demo application.
const routes: Routes = [
{path: '', redirectTo: 'home', pathMatch: 'full'},
{
path: 'home',
canActivate: [() => inject(AuthGuard).canActivate()],
We add the recommended autocomplete
attribute on the input fields to help the integrated password managers retrieve the correct fields on the login form.
<ion-item>
<ion-input [required]="true" autocomplete="username" label="Username" labelPlacement="stacked" name="username"
ngModel type="text"></ion-input>
</ion-item>
<ion-item>
<ion-input [required]="true" autocomplete="current-password" label="Password" labelPlacement="stacked"
name="password" ngModel
type="password"></ion-input>
</ion-item>
Because only Chrome has an implementation for PasswordCredential, we need to treat this feature as a progressive enhancement and add a feature detection check before accessing the API. This way, the application works in any browser, but only on Chrome, we are going to see the auto sign-in process.
if ((window as any).PasswordCredential) {
Chrome password settings ¶
In Chrome, we have two settings that control the behavior of the integrated password manager.
Open the settings page with chrome://settings/passwords
.
The first option enables or disables the password manager, and the second option enables auto sign-in. We will see how these options, especially the second one, affect the behavior of our demo application.
Without Credential Management API ¶
We enable the password manager for the first test and disable the auto sign-in option (according to the screenshot above). Additionally, we don't access the Credential Management API in the application.
When we log in, Chrome's password manager recognizes the login request and opens a dialog where it offers to save the credentials.
If the user saves username and password and then opens the login page later, Chrome auto-fills the fields with the stored credentials, the user only has to click or tap on Login and is logged in.
The password manager also supports multiple accounts per site. In that case, the user sees the following dialog when he focuses the username field.
Credential Management API: Store Credentials ¶
Next, we add code to our Ionic application that stores the password credential after the server successfully approves the login request.
private async storePassword(username: string, password: string): Promise<Credential | null> {
// @ts-ignore
if (!window.PasswordCredential) {
return Promise.resolve(null);
}
// @ts-ignore
const cred = new window.PasswordCredential({
id: username,
password,
name: username
});
return navigator.credentials.store(cred);
}
We first add a feature detection check, so the application also runs on browsers that do not support this feature.
The application then creates a PasswordCredential
object and stores it with navigator.credentials.store
into the password manager.
When we run the application, we see the exact behavior as before. The browser displays the Save Password dialog.
Even with the explicit call to navigator.credentials.store
, the user is still in charge of the
workflow. He can deny the request or allow it. There is no option to store the credential silently.
The benefit of storing the credentials via JavaScript is that the browser no longer has to guess which are the correct fields (username and password) for logging in to this application.
Credential Management API: Retrieve Credentials ¶
As mentioned at the beginning, we can not only store credentials we can also retrieve
them from JavaScript with the navigator.credentials.get
method.
With this capability, we can implement an auto sign-in solution that skips the login page when the password manager contains stored credentials for this site.
A good place for this to implement is the router guard. When the user navigates to the home
page, Angular first executes the canActivate
method of the guard.
The application first checks if the user is already logged in.
canActivate():
Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
if (this.authService.isLoggedIn()) {
return true;
}
return this.authService.isAuthenticated().pipe(
mergeMap(success => {
if (success) {
return of(true);
}
return this.tryAutoSignIn();
}),
map(success => {
if (success) {
return true;
}
return this.router.createUrlTree(['/login']);
})
);
}
If not, the code tries to fetch the password credentials from the password manager with navigator.credentials.get
. If the method returns a credential, the application sends a login request to the server.
// @ts-ignore
if (!window.PasswordCredential) {
return of(false);
}
// @ts-ignore
return from(navigator.credentials.get({password: true}))
.pipe(
mergeMap(cred => {
if (cred) {
// @ts-ignore
return this.authService.login(cred.name, cred.password);
}
return of(false);
}
)
);
}
If you run this code and navigate to the home page (/
or /#/home
), the Chrome browser
displays the following dialog when our code calls the navigator.credentials.get
method, and there are credentials stored.
The method returns the credential if the user clicks on Sign in, and our application sends a login request to the server. If that request is successful, the application displays the home page.
If the user cancels the "Sign in as" dialog, the get
method returns null, and the program displays the login page. The same happens when the browser does not support the PasswordCredential
API.
Auto Login ¶
This was already an improvement because we can skip the login page if credentials are stored in the browser, but the user still has to click on the Sign-in button.
The user can enable the second option (Auto Sign-in) on the password settings page to make this even more convenient.
The call to navigator.credentials.get
now no longer displays the "Sign in as" dialog and instead
immediately returns either the stored credentials or null.
The browser displays the following dialog to inform the user that a sign-in occurred.
Note that when multiple accounts are stored for a site, the browser displays the "Sign in as" dialog, even when the "Auto Sign-in" feature is enabled.
This concludes this tutorial about integrating the Credential Management API with a traditional username/password login flow.
See also the following two articles for more information about the API:
- https://web.dev/articles/security-credential-management
- https://medium.com/dev-channel/sign-in-on-the-web-credential-management-api-and-best-practices-d21aed14b6fe
The source code for the demo application is hosted on GitHub:
https://github.com/ralscha/blog2019/tree/master/credential