Using GoogleAnalytics with a Service Account - google-analytics-api

I'm trying to use the Google.Apis.Analytics.v3 client Library to retrieve metrics and I would like to work with a Service Account.
According to the documentation and several questions here on StackOverflow, I assume this should work. I have already used Service Accounts in the past against the Google BigQuery and Google Cloud Storage APIs, so I thought I could make it to work quite easily.
Unfortunately, each time I perform an operation against Google Analytics, I receive the following error:
TokenResponseException:
Additional information: Error:"invalid_grant", Description:"", Uri:""
For the record, here what I have done:
Created a brand new project in Google Developer Console.
Enabled Google Analytics API.
Create a "Service Account" Client ID.
Take note the "Client ID" the certificate and its password.
Add the Service Account "Email address" as a user with full permission on Google Analytics Account.
Here is the code I'm using to connect to Google Analytics:
private static AnalyticsService ConnectServiceAccount()
{
const string serviceAccountId = "xxxxxxxxxx-xxxxxxxxxxxx.apps.googleusercontent.com";
const string serviceAccountCertificate = "xxxxxxxxxxxxxxxxxxxxxxxxxx-privatekey.p12";
const string applicationName = "";
var certificate = GetServiceAccountCertificate(serviceAccountCertificate);
var credentials = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountId)
{
Scopes = new[] {
AnalyticsService.Scope.Analytics,
AnalyticsService.Scope.AnalyticsEdit,
AnalyticsService.Scope.AnalyticsManageUsers,
AnalyticsService.Scope.AnalyticsProvision,
AnalyticsService.Scope.AnalyticsReadonly
},
}.FromCertificate(certificate)
);
var service =
new AnalyticsService(
new BaseClientService.Initializer
{
GZipEnabled = true,
HttpClientInitializer = credentials,
});
return service;
}
Can someone help me troubleshoot this issue?

You can find the solution here
The service account is an email address which is like #developer.gserviceaccount.com

Looks like you are using the wrong serviceAccountID. It should be the one ending in #developer.gserviceaccount.com
And, you only need to give that email Read and Analyze rights not full control in your analytics account.
I also thought it needed an applicationName. This is the app name in your GA console.

Related

Running Google Apps Script through https request with Service Account credentials

I'm working on a Flutter app. And I've been trying to run my web-app Google Apps Script through http request since I'm required to use a Service Account and that access isn't supported in the Apps Script API. But I keep getting a 403/Forbidden response to the requests. I have the credentials for the Service Account and I am using its access token in my request but it still doesn't work.
I'm a novice at http requests and new to Google's authentication protocols so I'd appreciate some insight.
Thanks in advance.
Code:
return await driveUtils.getCreds(context).then((creds) async {
final drive_scopes = [drive.DriveApi.DriveReadonlyScope, "https://www.googleapis.com/auth/drive.file"];
final script_scopes = [app_scripts.ScriptApi.ScriptDeploymentsScope];
return await clientViaServiceAccount(creds, script_scopes+drive_scopes).then((AuthClient client) async {
debugPrint("url = " + url);
debugPrint("token = " + client.credentials.accessToken.data);
return await client.get(url,
headers: {
"Authorization": "Bearer ${client.credentials.accessToken.data}"
}
);
}, onError: onClientError);
}, onError: onCredsError);
Background: The script creates a Form and sets it Destination to a Spreadsheet's ID. Hence, the app requires that anyone who runs it to have a Google account to become the owner of the new Form and obtain access to the Sheet.
Update: It seems that Service Accounts can only access scripts that are within the same Google Cloud Project. This is a big issue since the point of the script is to create a central place for acquiring Form creation functionality for my app. And the app is intended to be used by anyone.
Does anyone have any ideas? Assuming a Service Account is the right Google Credentials for my app, I essentially need the ability to:
Create a Form that can be assigned to a user
Designate a user's spreadsheet as the forms response location
Retrieve the forms publishedURL
#Tanaike helped me figure out the issue. In order to make the script visible and able to run with a Service Account I had to change the Share setting for viewing the script. Simple solution

How to impersonate an admin user when using getClient() in the Google API NodeJS client

Per the recommendation in the defaultauth sample, I am trying to access the directory api for a domain which I have created a service account for. Here is the code I am attempting to connect with:
import { google } from 'googleapis'
const authClient = await google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/admin.directory.user.readonly']
})
const service = google.admin('directory_v1')
console.log(
await service.users.list({
auth: authClient,
domain: <redacted>
})
)
However, when I attempt to connect I recieve an error saying Error: Not Authorized to access this resource/api. If I remove the creds.json file in ~/.google, the error changes to saying that it cannot find the credentials file. Also, I am able to access a bucket using the same file, so I'm pretty sure my local environment is set up correctly, authentication wise. I have also worked for the past few days with someone on the support team G Suite API team, who assures me that things are set up correctly on my domain.
After looking around online, it seems the thing I am missing is impersonating an admin account when trying to connect with my service-account. I have found a few examples online of doing this with a JWT auth strategy, but I would like to continue to use the default auth client, in order to abstract away the implementation details. Is this possible? If so, what do I have to change? I have tried setting subject, and delegationEmail in both of the calls (getClient and list).
Any help would be greatly appreciated.
Just set subject of the client object:
authClient.subject = 'your email address'
Google's api documentations highly varies by language. No standart. Something documented in PHP client may be missing in nodejs client and it can take hours to find out how to do it.
You can pass clientOptions.subject in the constructor.
import { google } = from 'googleapis';
const authClient = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/admin.directory.user.readonly'],
clientOptions: {
subject: "your email address"
});

From where get subscription key for api.ai chatbot to be deployed on Microsoft Cortana

I'm trying to build a chatbot using api.ai. I want to integrate this bot with Microsoft Cortana. I followed every step given at this link: https://docs.api.ai/docs/cortana-integration but then to initialize api.ai instance, I have written following code. Client access token is available to me. My question here is from where to get the subscription key?
var config = new AIConfiguration("String subscription key",
"String client_access_token",
SupportedLanguage.English);
apiAi = new ApiAi(config);
apiAi.DataService.PersistSessionId();
Are you sure about the code that you have because at the URL mentioned : https://docs.api.ai/docs/cortana-integration, the code is something like this:
var config = new AIConfiguration("YOUR_CLIENT_ACCESS_TOKEN", SupportedLanguage.English);
So, all you will need is just the Client Access Token, which is available from the Agent Settings page in API.AI

How can I use IdentityServer3 External Authentication to login a Local User?

Having worked through the IdentityServer3 MVC tutorial here I have a working sample application where I can log in as 'bob', or I can log in with any google account.
What I am trying to work out is, say bob has an additional field 'GoogleId':
public static class Users
{
public static List<InMemoryUser> Get()
{
new InMemoryUser
{
Username = "bob",
Password = "secret",
Subject = "1",
GoogleId = "60a71ff098f6509cbd4fbda2f495eacb",
Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Bob"),
new Claim(Constants.ClaimTypes.FamilyName, "Smith"),
new Claim(Constants.ClaimTypes.Role, "Geek"),
new Claim(Constants.ClaimTypes.Role, "Foo")
}
}
}
}
When I log in with Google, if the incoming subject claim matches a User's GoogleId, log in with the local user account.
The IdentityServer3.AspNetIdentity project must be doing this as the Asp.Net Identity has external logins keyed off local user accounts. However I can't use the Asp.Net Identity user model as I'm working in a legacy application. I can't understand the code there well enough to apply it to my situation.
Is there a sample or tutorial somewhere that demonstrates this scenario?
You will want to implement the AuthenticateExternalAsync method in the UserService. You can use the ExternalIdentity provided to find the local user by your own mechanism and then set an AuthenticateResult.
Here is a good example of a custom UserService authenticating externally.

Google OAuth API to get user's email address?

I am playing with Google's OAuth 2.0 Playground using my own personal Google account, but I cannot seem to recover my Gmail address using the playground.
The scope I am using is:
email profile https://www.googleapis.com/auth/plus.login
But when I call the API:
https://www.googleapis.com/oauth2/v2/userinfo
I get various information about the user such as family name, first name, gender, picture, etc. but it does not return the user's email.
How do I retrieve the user's email address? Do I have the wrong scope or am I calling the wrong API? I feel like this should be very simple but I have literally been trying to figure this out for hours and I cannot find an API and scope combination that consistently provides the user's email address.
Update: December 2018
On December 20th, Google announced that the Google+ API would be turned down in March 2019, with intermittent failure starting at the end of January 2019. As part of the the plus.people.get endpoint is deprecated and scheduled to be terminated.
The userinfo endpoint is de-deprecated (see clarification) and should provide the info assuming
You request the https://developers.google.com/identity/sign-in/web/devconsole-project scope and
You request the email field.
Clarification: 24 Jan 2019
Google documented that the userinfo (v2) endpoint was deprecated, but later changed it to "deprecated, but kept available for backwards compatibility".
Current documentation discusses getting profile and email information through the currently supported openid method. This includes using the "userinfo" endpoint specified in their discovery document, as required by OpenID Connect.
At the moment, that URL is https://openidconnect.googleapis.com/v1/userinfo, but this has changed in the past and the discovery document at https://accounts.google.com/.well-known/openid-configuration is the authoritative source for the URL to use.
So, to be clear:
The old userinfo URL is maintained for backwards compatibility
The new userinfo URL is available at the discovery document
Regardless, the plus version of anything (described below) is deprecated and scheduled to be removed.
Original Answer
There are a lot of issues here in what you're doing and how you're trying to do it.
For starters, the https://www.googleapis.com/oauth2/v2/userinfo endpoint is deprecated, and scheduled to be removed in September 2014. It has begun working inconsistently - so don't use it.
As #abraham noted, you'll use the people.get endpoint at https://www.googleapis.com/plus/v1/people/me. This should give you the emails field containing an array of addresses. In your case, there will likely be only one that has a type of "account".
As of 2017: use the email scope. See Authorizing API requests.
This email scope is equivalent to and replaces the
https://www.googleapis.com/auth/userinfo.email scope.
For signing in with Google using OAuth 2.0, there's no need to make a separate request to get user's email.
When Google calls the callback URL, it provides a code in the query string that you could use to exchange for access token and ID token. The ID token is a JWT that contains identity information about the user, which includes the email address.
See more information here: https://developers.google.com/identity/protocols/oauth2/openid-connect
You'll want to add the https://www.googleapis.com/auth/userinfo.email scope or replace https://www.googleapis.com/oauth2/v2/userinfo with it. If you're using the HTML example they provide, you can list multiple scopes separated by a space.
<span
class="g-signin"
data-callback="signInCallback"
data-clientid="{{ plus_id }}"
data-cookiepolicy="single_host_origin"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-scope="https://www.googleapis.com/auth/plus.login
https://www.googleapis.com/auth/userinfo.email">
</span>
To retrieve the email address, you need to include the scope: "https://www.googleapis.com/auth/userinfo.email" as mentioned in this document. If this scope is included while you generate the refresh token, you should be able to get the email address of the authenticating user by making the following request:
you can call this with your own access token then will give the response
https://www.googleapis.com/oauth2/v3/userinfo?access_token="YOUR_ACCESS_TOKEN"
response will look like this
{
"sub": "1057abc98136861333615xz",
"name": "My Name",
"given_name": "My",
"family_name": "Name",
"picture": "https://lh3.googleusercontent.com/a-/AOh14qiJarwP9rRw7IzxO40anYi4pTTAU_xseuRPFeeYFg",
"email": "MyName#gmail.com",
"email_verified": true,
"locale": "en"
}
or simply you can just write a function
import requests
def get_user_email(access_token):
r = requests.get(
'https://www.googleapis.com/oauth2/v3/userinfo',
params={'access_token': access_token})
return r.json()
I came here looking why my server did not get email in response to /oauth2/v2/userinfo api call. It was only once that I saw this & it has been working well in past.
The answer gave good lead. While fixing this, there were several other resources that helped. Still I am not sure whether expecting always email in the response is ok. so - put error handling in code in case emails are not returned.
Google api documentation about migrating to google+ signin.
https://www.googleapis.com/auth/userinfo.email scope
People resource documentation
Add google+ api to the project using google developer console. The complimentary (quota) of calls is quite high (20m for google+ signin api per day).
Add error handling & logging in server code in case api returns no emails. In my case, I was looking only type='account' email.
This is actually a bit of a challenge as Google does not provide an email by default. You must specifically request it from Google Plus.
const scope = [
'https://www.googleapis.com/auth/plus.me', // request access here
'https://www.googleapis.com/auth/userinfo.email',
];
auth.generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: scope,
});
const plus = google.plus({ version: 'v1', auth });
const me = await plus.people.get({ userId: 'me' });
const userEmail = me.data.emails[0].value;
There is a full version in this blog post I wrote: https://medium.com/#jackscott/how-to-use-google-auth-api-with-node-js-888304f7e3a0
by using google nodejs sdk:
const {google} = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
googleClientIdPublic,
googleClientSecret,
googleRedirectUriPublic
);
//scope you need: https://www.googleapis.com/auth/userinfo.email
oauth2Client.setCredentials(tokens);
const googleAuth = google.oauth2({
version: "v2",
auth: oauth2Client,
});
const googleUserInfo = await googleAuth.userinfo.get();
const email = googleUserInfo.data.email;
more info
I have been following Prisoner's answer right above, and it helped me... until I received the email from Google Developers about how Google+ API will be shutdown on March 7, 2019.
I scrounged around and found this solution to get the email using an id_token that is returned when you authorize an app with the email scope on your developer console.
From Google Sign-in for Websites:
To validate an ID token in PHP, use the Google API Client Library for
PHP. Install the library (for example, using Composer):
composer require google/apiclient
Then, call the verifyIdToken() function. For example:
require_once 'vendor/autoload.php';
// Get $id_token via HTTPS POST.
$client = new Google_Client(['client_id' => $CLIENT_ID]); // Specify the CLIENT_ID of the app that accesses the backend
$payload = $client->verifyIdToken($id_token);
if ($payload) {
$userid = $payload['sub'];
// If request specified a G Suite domain:
//$domain = $payload['hd'];
} else {
// Invalid ID token
}
This will return an array that contains the user information, that also contains the email of the user who logged in. Hope this helps anyone else.
Please see my answer here to the identical issue:
how to get email after using google OAuth2 in C#?
In your scopes variable. Use the value "email" not the
full https address. Scope keywords in the web link are separated by spaces. I solve your issue with scopes written as: profile email openid.
https://developers.google.com/gmail/api/v1/reference/users/getProfile
For gmails api, add this to nodejs code:
function getUsersEmail (auth) {
const gmail = google.gmail({version: 'v1', auth})
gmail.users.getProfile({
userId: 'me'
}, (err, {data}) => {
if (err) return console.log('The API returned an error: ' + err)
console.log(data.emailAddress)
})
}
Gmails api: https://developers.google.com/gmail/api/guides/
Change the authorizationRequest with given scope: scope=openid%20email%20profile and use userinfoapi. This link worked for me
I suggest the following minimal code, which include '*/userinfo.email' and '#google-cloud/local-auth' package:
const path = require('path');
const { google } = require('googleapis');
const { authenticate } = require('#google-cloud/local-auth');
const scope = [
'https://www.googleapis.com/auth/userinfo.email'
];
async function runSample() {
const auth = await authenticate({
keyfilePath: path.join(__dirname, 'oauth2.keys.json'),
scopes: scope
});
google.options({ auth });
const dat = await google.oauth2('v2').userinfo.get()
console.log(dat.data.email);
}
if (module === require.main) {
runSample().catch(console.error);
}
module.exports = runSample;