Search code examples
phpsingle-sign-ongoogle-appsgoogle-apps-marketplace

Google Apps Marketplace SSO: offline access


Our application is widely-integrated with Google services like Mail, Contacts, Calendar etc. And for all of these Google services we need access to the user's data permanently, without asking the user for permission every time we need to pull our app with data from Google. It's very important to have instant access to the user's data when we sync contacts, calendar or access mail via Google IMAP. We also provide access to our app from any Google service via Single-Sign-On (SSO). And here we have a trouble. We are asking users who install our application for "offline" access to their data. So we can have access to their data instantly, when we run our sync scripts etc. First time user grants access to our app (via SSO, when it clicks on our app icon), it's asked to grant access to all data API we need - contacts, email, calendar etc. Everything goes okay and user gets logged in our app. But when user accesses our app next times (same via SSO), it gets asked to grant "Have offline access". It's very strange, because first time user doesn't get asked for this. And it also doesn't look nice to ask user every time for "offline access". Why we consider such behaviour strange is because once user has already granted access. So why it gets asked again and again?

The code we are using for SSO is as follows:

$clientId     = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com";
$clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
$callback     = "https://our_domain/callback";
$scopes       = array(
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/userinfo.profile",
    "https://www.googleapis.com/auth/drive.readonly.metadata",
    "https://www.googleapis.com/auth/calendar",
    "https://www.google.com/m8/feeds",
    "https://www.googleapis.com/auth/tasks",
    "https://www.googleapis.com/auth/admin.directory.user.readonly",
    "https://mail.google.com/"
);

// $params is a list of GET and POST parameters

if (empty($params['code']))
{
    $_SESSION['GOOGLE_SSO_STATE'] = md5(uniqid(rand(), true));

    $client = new Google_Client();

    $client->setClientId($clientId);
    $client->setRedirectUri($callback);
    $client->setAccessType('offline');
    $client->setApprovalPrompt('force');
    $client->setState($_SESSION['GOOGLE_SSO_STATE']);
    $client->setScopes($scopes);

    $url = $client->createAuthUrl();

    echo "<script> top.location.href='" . $url . "'</script>";
    exit;
}

if (!empty($params['code']) && !empty($params['state']) && $params['state'] == $_SESSION['GOOGLE_SSO_STATE'])
{
    unset($_SESSION['GOOGLE_SSO_STATE']);

    $client = new Google_Client();
    $client->setClientId($clientId);
    $client->setClientSecret($clientSecret);
    $client->setRedirectUri($callback);

    $credentials = $client->authenticate();

    // we need refresh token so we can exchange it for new access token when the current ones expires
    if (!isset($credentials_['refresh_token']))
    {
        echo "Wrong credentials received";
        exit;
    }

    $client = new Google_Client();
    $client->setUseObjects(true);
    $client->setAccessToken($credentials);

    $userInfoService = new Google_Oauth2Service($client);

    $userInfo = $userInfoService->userinfo->get();

    echo $userInfo->getId();
}

Can anyone help us understand this behaviour? Or maybe someone even knows how to make it not asking user for "offline access" every time it accesses the app?


Solution

  • This because your approval prompt is set to "force:

     $client->setApprovalPrompt('force');
    

    Use force only if you need to re-issue a refresh token. There is a strange side effect that ask the user time and again for offline access.