Account linking with Google Sign-In

Google Sign-In for the Assistant provides the simplest and easiest user experience to users and developers both for account linking and account creation. Your Action can request access to your user's Google profile during a conversation, including the user's name, email address, and profile picture.

The profile information can be used to create a personalized user experience in your Action. If you have apps on other platforms and they use Google Sign-In, you can also find and link to an existing user's account, create a new account, and establish a direct channel of communication to the user.

To perform account linking with Google Sign-In, you ask the user to give consent to access their Google profile. You then use the information in their profile, for example their email address, to identify the user in your system.

Implement Google Sign-In account linking

Follow the steps in the following sections to add Google Sign-In account linking to your Action.

Configure the project

To configure your project to use Google Sign-In account linking, follow these steps:

  1. Open the Actions Console and select a project.
  2. Navigate to the Account linking section.
  3. In Account creation, select Yes, allow users to sign up for new accounts via voice.
  4. In Linking type, select Google Sign In.
  5. Open Client Information and take note of the value of Client ID issued by Google to your Actions.
  6. Click SAVE.

Start the authentication flow

Use the Account Sign-in helper intent to start the authentication flow.

After the user authorizes your action to access their Google profile, you will receive a Google ID token that contains the user's Google profile information in every subsequent request to your action.

To access the user's profile information, you need to first validate and decode the token by doing the following:

  1. Use a JWT-decoding library for your language to decode the token, and use Google's public keys (available in JWK or PEM format) to verify the token's signature.
  2. Verify that the token's issuer (iss field in the decoded token) is https://accounts.google.com and that the audience (aud field in the decoded token) is the value of Client ID issued by Google to your Actions, which is assigned to your project in the Actions on Google console.

The following is an example of a decoded token:

{
  "sub": 1234567890,        // The unique ID of the user's Google Account
  "iss": "https://accounts.google.com",        // The token's issuer
  "aud": "123-abc.apps.googleusercontent.com", // Client ID assigned to your Actions project
  "iat": 233366400,         // Unix timestamp of the token's creation time
  "exp": 233370000,         // Unix timestamp of the token's expiration time
  "name": "Jan Jansen",
  "given_name": "Jan",
  "family_name": "Jansen",
  "email": "jan@gmail.com", // If present, the user's email address
  "locale": "en_US"
}

If you use the Actions on Google client library for Node.js, it takes care of validating and decoding the token for you, and gives you access to the profile content, as shown in the following code snippet:

Dialogflow
const app = dialogflow({
  // REPLACE THE PLACEHOLDER WITH THE CLIENT_ID OF YOUR ACTIONS PROJECT
  clientId: CLIENT_ID,
})

// Intent that starts the account linking flow.
app.intent('Start Signin', conv => {
  conv.ask(new SignIn('To get your account details'))
})

// Create a Dialogflow intent with the `actions_intent_SIGN_IN` event.
app.intent('Get Signin', (conv, params, signin) => {
  if (signin.status === 'OK') {
    const payload = conv.user.profile.payload
    conv.ask(`I got your account details, ${payload.name}. What do you want to do next?`)
  } else {
    conv.ask(`I won't be able to save your data, but what do you want to do next?`)
  }
})
Actions SDK
const app = actionssdk({
  clientId: CLIENT_ID,
})

app.intent('Start Signin', conv => {
  conv.ask(new SignIn('To get your account details'))
})

app.intent('actions.intent.SIGN_IN', (conv, input, signin) => {
  if (signin.status === 'OK') {
    const payload = conv.user.profile.payload
    conv.ask(`I got your account details, ${payload.name}. What do you want to do next?`)
  } else {
    conv.ask(`I won't be able to save your data, but what do you want to do next?`)
  }
})

Handle data access requests

To handle data access request, just verify that the user asserted by the Google ID token is already present in your database. The following snippet of code shows an example of how to check if a user account already exists in a Firestore database.

const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp();
const auth = admin.auth();
const db = admin.firestore();

// Save the user in the Firestore DB after successful signin
app.intent('Get Sign In', async (conv, params, signin) => {
  if (signin.status !== 'OK') {
    return conv.close(`Let's try again next time.`);
  }
  const color = conv.data[Fields.COLOR];
  const {email} = conv.user;
  if (!conv.data.uid && email) {
    try {
      conv.data.uid = (await auth.getUserByEmail(email)).uid;
    } catch (e) {
      if (e.code !== 'auth/user-not-found') {
        throw e;
      }
      // If the user is not found, create a new Firebase auth user
      // using the email obtained from the Google Assistant
      conv.data.uid = (await auth.createUser({email})).uid;
    }
  }
  if (conv.data.uid) {
    conv.user.ref = db.collection('users').doc(conv.data.uid);
  }
  conv.close(`I saved ${color} as your favorite color for next time.`);
});

// Retrieve the user's favorite color if an account exists, ask if it doesn't.
app.intent('Default Welcome Intent', async (conv) => {
  const {payload} = conv.user.profile;
  const name = payload ? ` ${payload.given_name}` : '';
  conv.ask(`Hi${name}!`);
  // conv.user.ref contains the id of the record for the user in a Firestore DB
  if (conv.user.ref) {
    const doc = await conv.user.ref.get();
    if (doc.exists) {
      const color = doc.data()[Fields.COLOR];
      return conv.ask(`Your favorite color was ${color}. ` +
        'Tell me a color to update it.');
    }
  }
  conv.ask(`What's your favorite color?`);
});