In this codelab, you'll learn how to implement the Credential Management API and bring automatic sign-in to an existing website built with a traditional architecture.

What you'll learn

What you'll need

Make sure your "Auto Sign-In" is enabled in Chrome. Find the setting from chrome://settings/passwords .

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would rate your experience with building web apps?

Novice Intermediate Proficient

While you can enable auto sign-in with the Credential Management API remotely from your laptop, this codelab uses Google Cloud Shell, a command line environment running in the Cloud. Google Cloud Shell is a Debian-based virtual machine that offers a persistent 5GB home directory and greatly enhances network performance and authentication. All you will need for this codelab is a browser (yes, it works on a Chromebook).

Set up a project in Cloud Console

If you don't already have a Google Account (Gmail or Google Apps), you must create one. Sign-in to Google Cloud Platform console (console.cloud.google.com) and create a new project:

Remember the project ID, a unique name across all Google Cloud projects.

Set up credential information for Google Sign-In

Open the APIs & services from the menu to the left and choose "Credentials".

Then select "OAuth consent screen" tab, specify "Product name shown to users". This can be anything, but let's name it as "Credential Management Codelab" at this time. Then "Save".

Now let's activate the Cloud Shell. Simply click the button on the top right-hand side (it will take a few moments to provision and connect to the environment):

Press "Start Cloud Shell" when prompted. Once you have the Cloud Shell open, click on the preview button to open a browser window. Don't worry about the error message, this will be fixed.

We will use this browser window to preview the app. Copy or take note of domain part of the URL in the browser, as you will use it later. It should look something like this.

Go back to Credentials screen and select "Credentials" tab, click "Create credentials", and choose "OAuth client ID":

Select "Web application" type and enter the following in the form:

Once you have finished filling those fields, "Create" it, then download the JSON file with the credential information by clicking on the icon next to the entry on the right. It should be named something like client_secret_******.apps.googleusercontent.com.json. We'll use this JSON file's content in the next section.

Clone the repo

Come back to the Cloud Shell. We'll finally build the base code.

Firstly, git clone the repository:

$ git clone https://github.com/googlecodelabs/credential-management-api.git

Once the code is cloned, go into the directory and set up the environment.

$ cd credential-management-api
$ virtualenv env
$ source env/bin/activate
$ npm install

Create client_secrets.json

Finally, create a text file at root of the project with a name client_secrets.json and copy/paste the content of the JSON file you just downloaded named client_secret_******.apps.googleusercontent.com.json.

Try the base code

Run the base code with the following command:

$ npm start

You should be able to see it's running in preview window.

Let's explore the website and examine what's happening and what we will be doing. Open the Cloud Shell code editor by clicking the "edit" icon at the top right corner of the console.

To see current server side code, examine working/main.py.

Top page: /

This page will be displayed when you first open the website. You are signed out and there's a registration form. Signed in users will be redirected to /main.

Register endpoint: /register

By filling the form and pressing a "Register" button at the top page (/), the form will send an AJAX POST to this endpoint. You will be then redirected to the main page (/main) if the request is successful or will show an error message if it fails.

Sign-in page: /signin

You can visit this page by tapping on the "Sign In" button at the top right corner of the top page (/).

ID/password sign-in endpoint: /auth/password

By filling in the form and pressing the "Sign In" button at the sign-in page (/signin), an AJAX POST is sent to this endpoint. You will then be redirected to the main page (/main) if the request is successful or shown an error message if it fails.

Google Sign-In endpoint: /auth/google

By tapping on "Sign In with Google" button at either the top page or the sign-in page, a window pops up and lets you sign in using a Google account. Once complete, an AJAX POST is sent to this endpoint. You will then be redirected to the main page (/main) if the request is successful or shown an error message if it is a failure.

Main page: /main

You can only visit this page if you are signed in.

Sign-out endpoint: /signout

By tapping on the "Sign Out" button at the main page, an AJAX request is sent to this endpoint, sign out, then redirected to /.

Unregister endpoint: /unregister

By tapping on the "Unregister" button at the main page, an AJAX request is sent to this endpoint, unregister, then redirected to /.

In this step, you will implement a credential storing feature on user registration.

Detect Credential Management API feature

In order to progressively enhance your website to support the Credential Management API, let's add a variable that returns API availability.

At TODO 4-1 in static/scripts/app.js, add the following code:

var cmapiAvailable = navigator.credentials &&
                     navigator.credentials.preventSilentAccess;

It's important to detect preventSilentAccess since Chrome added backward incompatible changes in version 60. Without it, it may break things in previous versions of Chrome (there are still users of older Chrome versions).

Store the credential upon successful registration

Open static/scripts/index.js. You can find the code that registers a user using an AJAX call. Store a credential upon successful registration by creating a PasswordCredential object then calling navigator.credentials.store().

Replace TODO 4-2 of static/scripts/index.js with the following code:

      if (cmapiAvailable) {
        var cred = new PasswordCredential(regForm);
        navigator.credentials.store(cred)
        .then(function() {
          location.href = '/main?quote=You are registered';
        });
      } else {
        location.href = '/main?quote=You are registered';
      }

Add autocomplete attributes

The PasswordCredential object in index.js uses the form element to instantiate, but it requires mappings to ID, password and name. This can be done using semantics.

Add autocomplete attributes to respective input elements inside the form element in templates/index.html:

                  <input
                    id="csrf_token"
                    type="hidden"
                    name="csrf_token"
                    value="{{csrf_token}}" />
                  <paper-input
                    id="name"
                    name="name"
                    autocomplete="name"
                    label="Whatever name"
                    pattern=".{1,32}"
                    error-message="4-32 letters please"
                    auto-validate required></paper-input>
                  <paper-input
                    type="email"
                    id="email"
                    name="email"
                    autocomplete="username"
                    label="Fake email address"
                    error-message="at least email format, please"
                    auto-validate required></paper-input>
                  <paper-input
                    id="password"
                    type="password"
                    name="password"
                    autocomplete="new-password"
                    label="Bogus password"
                    pattern=".{4,32}"
                    error-message="4-32 letters please"
                    auto-validate
                    required></paper-input>

Try it out

Now you've implemented the code to store credentials on user registration. Try to register yourself and see if it pops up a password saving dialog:

If it's not working as expected, please check the step04 directory and see what went wrong.

If the save password prompt doesn't show up, check if your preference at chrome://settings/passwords is set properly once again.

In this step, you will implement the credential storing feature on user sign-in.

Store the credential upon successful sign-in

Open static/scripts/signin.js. You can find a similar code to what you've seen in previous section. This time you are submitting a different form which is for sign-in. The differences from the user registration are:

Replace TODO 5-1 of static/scripts/signin.js with following code.

      if (cmapiAvailable) {
        var cred = new PasswordCredential(form);
        navigator.credentials.store(cred)
        .then(function() {
          location.href = '/main?quote=You are signed in';
        });
      } else {
        location.href = '/main?quote=You are signed in';
      }

Add autocomplete attributes

Add autocomplete attributes to respective input elements inside the form element in templates/signin.html:

                  <input
                    id="csrf_token"
                    type="hidden"
                    name="csrf_token"
                    value="{{csrf_token}}" />
                  <paper-input
                    type="email"
                    id="lemail"
                    name="email"
                    autocomplete="username"
                    label="email"
                    error-message="at least email format, please"
                    auto-validate></paper-input>
                  <paper-input
                    id="lpassword"
                    type="password"
                    name="password"
                    autocomplete="current-password"
                    label="password"
                    pattern=".{4,32}"
                    error-message="4-32 letters please"
                    auto-validate
                    required></paper-input>

Try it out

You've now implemented a feature to store credentials when a user signs in. If there's already a stored credential, remove it either at the address bar by clicking on a key icon or at chrome://settings/passwords.

Then try to sign in at /signin using your registered ID/password combination and check if a password saving dialog pops up.

Notice that storing a credential only happens when the authentication is successful.

If it's not working as expected, check the step05 directory to see what went wrong.

By using the Credential Management API, you can let users sign in to a website without typing a password. It's especially helpful on mobile devices where typing with a touch screen is a cumbersome task.

Define autoSignIn() function

The core of the auto sign-in is the navigator.credentials.get() API. Let's define an autoSignIn() function that retrieves a credential and signs-in a user.

Open static/scripts/auto.js and replace TODO 6-1 with the following code:

var autoSignIn = function(mode) {
  if (cmapiAvailable) {
    // TODO 6-2: Obtain a credential
  } else {
    return Promise.reject();
  }
};

In order to retrieve a password credential, call navigator.credentials.get() with the option password: true. This tries to retrieve a credential object.

Replace TODO 6-2 with the following code:

    return navigator.credentials.get({
      // TODO 9-4: Enable auto sign-in with a federated account
      // TODO 8-3: Reflect a silent access
      password: true
    }).then(function(cred) {
      if (cred) {
        // TODO 6-3: Sign in the user
      } else {
        return Promise.reject();
      }
    // TODO 6-4: See if sign-in was successful
    });

Once you receive a credential object, you can sign-in the user. You can use fetch() or XMLHttpRequest to send credential information to the server.

Make sure to check the type of the credential object by examining the type property. If the type is password, it's a PasswordCredential. If the type is federated, it's a FederatedCredential.

Replace TODO 6-3 with the following code:

        var form = new FormData();
        var csrf_token = document.querySelector('#csrf_token').value;
        form.append('csrf_token', csrf_token);

        switch (cred.type) {
          case 'password':
            form.append('email', cred.id);
            form.append('password', cred.password);
            return fetch('/auth/password', {
              method: 'POST',
              credentials: 'include',
              body: form
            });
          // TODO 9-5: Enable auto sign-in with a federated account
        }
        return Promise.reject();

Check the status code from fetch(), and only resolve if the request was successful.

Replace TODO 6-4 with the following code:

    }).then(function(res) {
      if (res.status === 200) {
        return Promise.resolve();
      } else {
        return Promise.reject();
      }

We now have the autoSignIn() function. This will be used in 2 scenarios.

The first is when a user taps on "Sign-In" button. autoSignIn() shows an account chooser and let the user select one to sign in with.

The second is when a user lands on the website. autoSignIn() lets the user sign in automatically without any explicit actions.

Sign-In a user by pressing a "Sign-In" button

Open static/scripts/index.js. Existing code simply redirects a user to the /signin page where the sign-in form exists. Let's add the autoSignIn() function here and attempt to let the user sign-in. Only transition the user to the sign-in page if the attempt fails.

Replace TODO 6-5 with the following code:

  autoSignIn()
  .then(function() {
    location.href = '/main?quote=You are signed in';
  }, function() {
    location.href = '/signin';
  });

Try it out

You've implemented a mediated sign-in feature. Try to sign out and see if it works.

If it's not working as expected, check the step06 directory to see what went wrong.

You can be more aggressive and let users sign-in upon landing at your website. Let's enable auto sign-in.

Sign-In a user upon landing the page

Open static/scripts/auto.js again and replace TODO 7-1 with the following code:

// TODO 9-3: Defer autoSignIn() until Google Sign-In is initialized
// TODO 8-4: Reflect a silent access
autoSignIn().then(function() {
  location.href = '/main?quote=You are automatically signed in';
}, function() {
  console.log('auto sign-in skipped');
});

If autoSignIn() resolves, the user will be forwarded to the main page. Otherwise, a message is logged to the console.

Try it out

Remove all cookies by opening the Developer Tools and go to Application > Clear Storage, click "Clear site data" and reload. You should notice that you are automatically signed in without any explicit actions.

But now, try to sign out by pressing the "Sign Out" button. What happened? You must have been signed back in again, automatically.

This is annoying, but don't worry it's expected. How can we fix this behavior?

Check step07 directory to see if anything else is odd other than the case described above.

In order to prevent the unexpected auto-signed-back-in behavior, you can turn it off. By doing so, sign-ins will only occur with an explicit user action.

Turn off auto sign-in when a user signs out

Open static/scripts/main.js and replace TODO 8-1 with the following code:

  if (cmapiAvailable) {
    navigator.credentials.preventSilentAccess()
    .then(function() {
      location.href = '/signout';
    });
  } else {
    location.href = '/signout';
  }

navigator.credentials.preventSilentAccess() turns off auto sign-ins and returns a promise. Once it resolves, you can forward the user to /signout.

Turn off auto sign-in when a user unregisters

Keep working on static/scripts/main.js and replace TODO 8-2 with the following code:

      if (cmapiAvailable) {
        navigator.credentials.preventSilentAccess()
        .then(function() {
          location.href = '/?quote=You are unregistered';
        });
      } else {
        location.href = '/?quote=You are unregistered';
      }

This calls navigator.credentials.preventSilentAccess() when the user is successfully unregistered.

Reflect a silent access

navigator.credentials.get() has an option called mediation to change its behavior when auto sign-in is turned off. When it's set to 'silent', the API will respect the auto sign-in turn off (previous navigator.credentials.preventSilentAccess() call) and simply return undefined.

Open static/scripts/auto.js and add mediation as an option of navigator.credentials.get() by replacing TODO 8-3 with the following code:

      password: true,
      mediation: mode

Replace TODO 8-4 with the following code and use 'silent' for autoSignIn() on the landing page.

autoSignIn('silent').then(function() {

Try it out

You've implemented a feature that turns off auto sign-in. You should now be able to sign out without the unexpected sign-back-ins.

If it's not working as expected, compare your code with the step08 directory and see what went wrong.

You can now sign in and out without any problems as long as you are using ID/password. What about when using a Google account federation? Let's finally make Google Sign-In work with the Credential Management API and enable auto sign-in using federated accounts.

Store a credential on successful sign-in using Google Sign-In

Using Google's federation feature is out of scope for this codelab, but remember that we are using the Google Sign-In JavaScript library. You can find the Google Sign-In authentication part of the code in static/scripts/federation.js.

Open static/scripts/app.js. You can find the code that authenticates a user using Google Sign-In and an AJAX call to the server. Store a credential upon successful authentication by creating a FederatedCredential object then calling navigator.credentials.store().

Replace TODO 9-1 with the following code:

        if (cmapiAvailable) {
          var profile = googleUser.getBasicProfile();
          var cred = new FederatedCredential({
            id: profile.getEmail(),
            name: profile.getName(),
            iconURL: profile.getImageUrl(),
            provider: GOOGLE_SIGNIN
          });
          return navigator.credentials.store(cred);
        } else {
          return Promise.resolve();
        }

The FederatedCredential object can be instantiated by setting individual params. Use the GoogleUser object obtained through gSignIn() call. By returning a promise, you can forward the user to the main page (/main) if resolved. Otherwise, show an error.

Return a promise upon initializing Google Sign-In

When a user lands on the website, it's highly possible that Google Sign-In isn't ready before autoSignIn() is called because the initialization happens asynchronously. Use a promise to defer the call to autoSignIn() until after the Google Sign-In initialization.

Open static/scripts/federation.js and replace TODO 9-2 with the following code:

// Initialize Google Sign-In
var googleAuthReady = new Promise(function(resolve) {
  gapi.load('auth2', function() {
    gapi.auth2.init().then(function() {
      resolve();
    });
  });
});

Defer autoSignIn() until Google Sign-In is initialized

Using the googleAuthReady variable, you can defer calling autoSignIn() until the initialization finishes.

Open static/scripts/auto.js and replace TODO 9-3 with the following code:

googleAuthReady.then(function() {
  return autoSignIn('silent');
}).then(function() {
  location.href = '/main?quote=You are automatically signed in';
}, function() {
  console.log('auto sign-in skipped');
});

Enable auto sign-in with a federated account

Finally, let's enable auto sign-in using a federated account.

Open static/scripts/auto.js and replace TODO 9-4 with the following code:

      password: true,
      federated: {
        providers: [ GOOGLE_SIGNIN ]
      },
      mediation: mode

This adds Google as an identity provider in navigator.credentials.get(). It tells the function to return a federated credential if it is stored / selected.

Make sure to check the type of the credential object by examining the type property. If the type is password, it's a PasswordCredential. If the type is federated, it's a FederatedCredential.

You can also check the provider property to see which identity provider it is for.

Replace TODO 9-5 with the following code:

          case 'federated':
            switch (cred.provider) {
              case GOOGLE_SIGNIN:
                return gSignIn(cred.id)
                .then(function(googleUser) {
                  var id_token = googleUser.getAuthResponse().id_token;
                  form.append('id_token', id_token);
                  return fetch('/auth/google', {
                    method: 'POST',
                    credentials: 'include',
                    body: form
                  });
                });
            }

Try it out

You should now have a fully functional website with auto sign-in using the Credential Management API.

If it's not working as expected, check step09 directory to see what went wrong.

Play around and enjoy!

Your website is now ready to auto sign-in users!

What we've covered

Next Steps

Learn More