2
votes

Following documentation described here, I have account linking set up with implicit grants and find that it works well when testing with the browser / actions console, and also with the Google Home app for Android. Unfortunately on the iphone version of the app, user auth hangs most of the time. Feedback from actions on google support is that the problem is that google sign in flow is implemented in separate browser tab (window). On iphone you can't open 2 windows in SfariViewController, thus they are re-writing address of the first page and can’t finish sign in flow. This is known issue and they are not planning to change this. The solution is to implement sign in flow all in one browser window. I'm unclear how to do this and am looking for someone to share code behind the authorization URL you set up that works consistently on iphone. Below is the core of what I am using:

.html snippet:

<!DOCTYPE html>
<html>
<head>
  <title>Authorization Page</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="google-signin-client_id" content="948762963734-2kbegoe3i9ieqc6vjmabh0rqqkmxxxxx.apps.googleusercontent.com">
  <!-- <meta name="google-signin-ux_mode" content="redirect"> INCLUDING THIS META TAG BREAKS THE AUTH FLOW -->
  <script src="js/googleAuth.js"></script>
  <script src="https://apis.google.com/js/platform.js" async defer></script>
  <link rel="stylesheet" href="css/googleAuth.css">   
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Raleway">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">  
</head>
<body>
<header class="bgimg-1 w3-display-container w3-grayscale-min" id="loginScreen">
  <div class="w3-display-topleft w3-padding-xxlarge w3-text-yellow" style="top:5px"> 
    <span class="w3-text-white">Google Sign In</span><br>
    <span class="w3-large">Sign in with your Google account</span><br><br>
    <div class="g-signin2" data-onsuccess="onSignIn"></div><br><br>        
  </div>   
</header>
</body>
</html>

.js code snippet:

function onSignIn(googleUser) {
  var profile = googleUser.getBasicProfile();
  var id = profile.getId()
  var name = profile.getName()
  var email = profile.getEmail()
  var token = googleUser.getAuthResponse().id_token;
  var client_id = getQueryVariable('client_id')
  // vital-code-16xxx1 is the project ID of the google app
  var redirect_uri = 'https://oauth-redirect.googleusercontent.com/r/vital-code-16xxx1'
  var state = getQueryVariable('state')
  var response_type = getQueryVariable('response_type')

  // store the user's name, ID and access token and then sign out
  storeOwnerID (email, name, id, token, function() {
    // sign out
    var auth2 = gapi.auth2.getAuthInstance();
    auth2.signOut().then(function () {
      console.log('signed out')
    });
    // if this page was loaded by Actions On Google, redirect to complete authorization flow
    typeof redirect_uri != 'undefined' ? window.location = redirectURL : void 0    
  }) 
}

function getQueryVariable(variable) {
  var query = window.location.search.substring(1);
  var vars = query.split('&');
  for (var i = 0; i < vars.length; i++) {
    var pair = vars[i].split('=');
    if (decodeURIComponent(pair[0]) == variable) {
      return decodeURIComponent(pair[1]);
    }
  }
  console.log('Query variable %s not found', variable);
}
2
would you mind posting the snippet of code to reproduce the issue in your question? It is difficult to understand the problem otherwise. - TMSCH
@TMSCH I've included code snippets. TIA - Dana
When you say it breaks the Auth flow, do you mean the account linking flow? I think the issue is that after the Google Sign-In redirect, the "state", "response_type" and "client_id" query parameters are no more present in the URL. You have to store them in persistent storage (localStorage or indexedDb) before redirecting to Google Sign In, and read them from there after the redirect. Does that make sense? - TMSCH
This did end up being part of the issue. I will document the full solution below. - Dana
How to add account linking in mobile apps, I am developing iOS mobile app to use Dialogflow skill. I need to persist user request for userID, how I can achieve my requirement? - Abilash Balasubramanian

2 Answers

1
votes

@dana Have you tried adding meta tag?

<meta name="google-signin-ux_mode" content="redirect">
1
votes

With help from Google support & engineering, this is now resolved:

  1. As noted above, I had to include this meta tag: <meta name="google-signin-ux_mode" content="redirect">
  2. I needed to have https://my-auth-endpoint.com/ in my project's authorized redirect URI. It is not enough to have it only in Authorized javascript origins. The other key thing is to include the trailing slash, I hadn't originally and it will not work without it.

Below is the simple code foundation you can use to get a working version of an authorization endpoint for actions on google account linking:

.html:

<!DOCTYPE html>
<html>
<head>
  <title>Authorization Page</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="google-signin-client_id" content="948762963734-2kbegoe3i9ieqc6vjmabh0rqqkmxxxxx.apps.googleusercontent.com">
  <meta name="google-signin-ux_mode" content="redirect">
  <script src="js/googleAuth.js"></script>
  <script src="https://apis.google.com/js/platform.js" async defer></script>
  <link rel="stylesheet" href="css/googleAuth.css">   
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Raleway">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">  
  <script>
    sessionStorage['jsonData'] == null ? storeQueryVariables() : void 0
  </script>
</head>
<body>
<header class="bgimg-1 w3-display-container w3-grayscale-min" id="loginScreen">
  <div class="w3-display-topleft w3-padding-xxlarge w3-text-yellow" style="top:5px"> 
    <span class="w3-text-white">Google Sign In</span><br>
    <span class="w3-large">Sign in with your Google account</span><br><br>
    <div class="g-signin2" data-onsuccess="onSignIn"></div><br><br>        
  </div>   
</header>
</body>
</html>

.js:

// Retrieve user data, store to DynamoDB and complete the redirect process to finish account linking
function onSignIn(googleUser) {
  let profile = googleUser.getBasicProfile(),
      id = profile.getId(),
      name = profile.getName(),
      email = profile.getEmail(),
      token = googleUser.getAuthResponse().id_token,
      redirect_uri = 'https://oauth-redirect.googleusercontent.com/r/vital-code-16xxxx',
      jsonData = JSON.parse(sessionStorage['jsonData']),
      redirectURL = redirect_uri + '#access_token=' + token + '&token_type=bearer&state=' + jsonData.state

  // store the user's name, ID and access token
  storeUserData(email, name, id, token, function() {
    // sign out of google for this app
    let auth2 = gapi.auth2.getAuthInstance();
    auth2.signOut()
    // if this page was loaded by Actions On Google, redirect to complete authorization flow
    typeof redirect_uri != 'undefined' ? window.location = redirectURL : void 0    
  })   
}

// Store the user data to db
function storeUserData (email, name, id, token, callback) {
  // removed for simplicity
}

// Store URI query variable 'state' to browser cache
function storeQueryVariables() {
  let qvar = {
    'state': getQueryVariable('state')
  }
  storeLocally(qvar)
}

// Get any variable from incoming URI
function getQueryVariable(variable) {
  var query = window.location.search.substring(1);
  var vars = query.split('&');
  for (var i = 0; i < vars.length; i++) {
      var pair = vars[i].split('=');
      if (decodeURIComponent(pair[0]) == variable) {
          return decodeURIComponent(pair[1]);
      }
  }
  console.log('Query variable %s not found', variable);
}

// Store JSON object input to local browser cache 
function storeLocally (jsonData) {
  if (typeof(Storage) !== 'undefined') {
    sessionStorage['jsonData'] = JSON.stringify(jsonData)
  } else {
    console.log('Problem: local web storage not available')
  }
}