Google Authentication with Service Account fails (node.js) - node.js

I'm trying out a (node.js) sample app to authenticate against Google API and then make a Google Drive request. The sample I am trying to run is from the github readme of the googleapis node.js library using jwt:
var jwtClient = new googleapis.auth.JWT(
'123...xyz#developer.gserviceaccount.com',
'./key.pem',
null,
['https://www.googleapis.com/auth/drive'],
'my.personal#gmail.com');
jwtClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
return;
}
// Make an authorized request to list Drive files.
drive.files.list({ auth: jwtClient }, function(err, resp) {
// handle err and response
});
});
Authentication fails with:
{ error: 'unauthorized_client',
error_description: 'Unauthorized client or scope in request.' }
I'm not 100% sure about the 'my.personal#gmail.com'. Using my Client ID, I receive the error 'Invalid impersonation prn email address.'.
I have created service account client ID, service email and certificate fingerprints according to documentation. Do I have to specify additional things? Is my scope incorrect? If it is, what should it be?
Google Drive API is enabled in the Google Developer Console. I also activated the trial account.

Ugh, after trying many things, the result is rather simple: doing the above sample without 'impersonate'-email it just worked. Code:
var jwtClient = new googleapis.auth.JWT(
'123...xyz#developer.gserviceaccount.com',
'./key.pem',
null,
['https://www.googleapis.com/auth/drive']);
The example from the readme is available as a complete file inside examples (here).

Related

Node.js cannot access Google AdSense Management API. 'user does not have adsense account'

I'm trying to generate AdSense reports on web app hosted on node.js.
Since it's a server-to-server communication i need to use service account.
I followed this guide, but i always getting error:
{
domain: "global"
message: "User does not have an AdSense account."
reason: "noAdSenseAccount"
}
also tried this JSON Web Tokens Guide but also getting error above.
What i got:
The Adsense Management API is enabled.
Service account credentials are created
Added service account email address to google AdSense User Management
Tested in the Google API Explorer and it works just fine
screenshot
NOTE: its status stuck on "pending".
Here is the code i'm trying to connect and retrieve data with:
const googleAuth = require('google-auth-library');
const GOOGLE_KEYS = require(path.join(__dirname, '/credentials/jwt.keys.json'));
this.jwtClient = new googleAuth.JWT(
GOOGLE_KEYS.client_email,
null,
GOOGLE_KEYS.private_key,
['https://www.googleapis.com/auth/adsense'],
);
this.jwtClient.authorize(async (err: any, tokens: any) => {
if (err) throw new Error(err);
this.jwtClient.setCredentials(tokens);
const url = `https://www.googleapis.com/adsense/v1.4/accounts`;
const listData = await this.jwtClient.request({url})
.catch((err: any) => {
console.log(err, 'error');
res.status(500).json(err)
});
res.status(200).json(data);
});
My questions is:
1. Why is it stuck on "pending" status?
2. Is there a way to acceess API using service account credentials?
3. If yes which way i can achieve it?
The AdSense API doesn't support Service Accounts[1], so you'll need to make OAuth credentials using the Web App flow[2].

Getting contacts from Google api in node

I want to get the contacts using the google contacts api in nodejs, but there isn't any quickstart on the developer.google page for nodejs.
I have found this wrapper on github https://github.com/hamdipro/google-contacts-api but I don't understand it and I don't know how to use it.
Can anyone tell me what can I do?
Unfortunately, Google's official API for NodeJS doesn't support Contacts API. They instead use the People API. If you need to access "Other Contacts", you will need Contacts API.
You can still connect with Contacts API using the official googleapis library if you're already using it for other purposes by sending a request to the Contacts API after creating the auth client.
Given that you already have the access token of the user (e.g. if you generated it using Passport, here's the code:
const {google} = require("googleapis");
const authObj = new google.auth.OAuth2({
access_type: 'offline',
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
});
Refresh access token automatically before it expires
authObj.on('tokens', (tokens) => {
const access_token = tokens.access_token
if (tokens.refresh_token){
this.myTokens.refreshToken = tokens.refresh_token
// save refresh token in the database if it exists
}
this.myTokens.accessToken = tokens.access_token
// save new access token (tokens.access_token)
}
authObj.setCredentials({
access_token:this.myTokens.accessToken,
refresh_token:this.myTokens.refreshToken,
});
Make the request to Contacts API:
authObj.request({
headers:{
"GData-Version":3.0
},
params:{
"alt":"json",
//"q":"OPTIONAL SEARCH QUERY",
//"startindex":0
"orderby":"lastmodified",
"sortorder":"descending",
},
url: "https://www.google.com/m8/feeds/contacts/default/full"
}).then( response => {
console.log(response); // extracted contacts
});
First thing instead of going with non-official package mentioned in question you should prefer using official package as they are well maintained, every under the hood changes are handled properly and also issues created are taken into considerations.
Official package for same is here.
Now steps to use above package to get contacts of a user :-
Include googleapis using npm install googleapis --save
Create a service client
var google = require('googleapis');
var contacts = google.people('v1');
Authorise client to make request {Link for authentication docs}
Making authenticated requests
contacts.people.connections.list({
auth: oauth2Client //authetication object generated in step-3
}, function (err, response) {
// handle err and response
});
That should be enough to get user's contact data. Also for authentication if you are using this for domain apart from gmail and have admin access you can get all user's contacts using domain wide delegation otherwise you will have to manually allow access for each user.
Hope it helps. Let me know in comments if have any queries.

CRON Node.js Gmail API script

How do I properly setup Gmail API script that sends emails?
I am about to use this method and I started building my script from this quickstart guide.
Is there alternative way to do this without using OAuth 2 validation? Or a way to validate once for all?
Well, in using Gmail APi with your app, you need to use OAuth 2.0 because all request to the Gmail API must be authorized by an authenticated user. And if you notice the quickstart, there is a step here that you need to create a credentials/Outh client ID to make this API work.
For more information, there is another way to authorize your app with Gmail. You can do it with the help of Google+ Sign-in that provide a "sign-in with Google" authentication method for your app.
While asking for authorization from GMail, OAuth 2.0 gives one access token and one refresh token. To avoid validation every time, store the access token. Use the refresh token to get the new access token after it is expired (access token expires every one hour).
Read about this process here: https://developers.google.com/identity/protocols/OAuth2
I found solution using JWT to authorize OAuth2.
You need to have admin account to create Domain wide delegation service account. Then in Developer console you need to download service key JSON file which you load as credentials.
First fetch all users like this: (here you need to use account with admin directory rights)
const google = require('googleapis');
const gmail = google.gmail('v1');
const directory = google.admin('directory_v1');
const scopes = [
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/admin.directory.user.readonly'
];
const key = require('./service_key.json');
var authClient = new google.auth.JWT(
key.client_email,
key,
key.private_key,
scopes,
"authorized#mail.com"
);
authClient.authorize(function(err, tokens){
if (err) {
console.log(err);
return;
}
directory.users.list(
{
auth: authClient,
customer: 'my_customer',
maxResults: 250,
orderBy: 'email'
}, (err, resp) => {
if (err) {
console.log(err);
return;
}
console.log(resp);
});
});
Then you need to fetch Thread lists (100 per request (page)). And for each thread object you need to call get method for full thread. When using Gmail API authorize as user you want to fetch emails from. In request as userId use value 'me'.

Accessing Google Directory API with NodeJs

I am having trouble accessing the Google Directory API using node. What I am hoping to do is create and remove users from Groups (and create and list groups and their users). In testing I have managed to access most of the APIs without trouble but the Directory has been impossible.
Firstly, is what I am trying to do even possible?
Secondly, if it is possible, here is a sample of my code; what am I missing?
var google = require('googleapis');
var googleAuth = require('google-oauth-jwt');
var request = require('google-oauth-jwt').requestWithJWT();
request({
url: 'https://www.googleapis.com/admin/directory/v1/groups?domain=mydomainname.com&customer=my_customer',
jwt: {
email: 'created-service-account#developer.gserviceaccount.com',
keyFile: './MyPemFile.pem',
scopes: [
'https://www.googleapis.com/auth/admin.directory.orgunit',
'https://www.googleapis.com/auth/admin.directory.device.chromeos',
'https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.group',
'https://www.googleapis.com/auth/drive.readonly'
]}
}, function (err, res, body) {
if (err) console.log("Error", err);
console.log("BODY", JSON.parse(body));
});
I have created a project in the Developer Console. I have created a new clientId (Service Account). I am then presented with a p12 file, which I use openSSL to convert to a pem file (file path for this given in keyFile setting above). The clientId email address created is used in the email setting above.
I have granted the project access to the Admin SDK. I have then gone into Admin Console and in Security -> Advanced -> Manage API client access, I have granted the Service Account access to all the scopes requested in the above code.
Hope, this makes sense, it is difficult to describe the full process. Please comment if you have any questions or need clarity on anything.
When running this code I always get a 403, "Not Authorized to access this resource/api".
Am I using the correct methodology? It is difficult to follow the Google Documentation as not all of help files match the current menu system.

Google Plus auth for REST API

I'm trying to create a rest api for a service I'm working on.
The service has two parts to it - the website and the mobile client. Basically, the mobile device keeps its location up to date via the api, the website displays the data via the api.
Seeing as my application only targets Android, I'm hoping to use 'Sign in with Google' as the authentication mechanism for both the mobile and website clients.
The API is using Node.js and Express.js. I'm running into trouble when generating new user accounts though. Seeing as I don't want to trust data from the client, my expected sign up process was something like this:
Through the website:
User visits website, hits 'Sign up with Google'.
User accepts the app request to see their Google details.
Website gets a google auth token back, which it sends to the api.
API contacts google with that auth token to get the user details.
API creates a new user in the database along with my own form of access token.
API returns my own access token to the client for future request signing.
Through the Android app:
User downloads the app, runs and hits 'Sign up with Google'.
User accepts authorisation step presented by google.
App gets a token, which it sends to the API.
API contacts google with that auth token to get the user details.
API realises the user exists and registers this new device with that user.
API returns my own access token to the app for future request signing.
I'm running into a lot of trouble here as soon as the token gets to the server though. Every time I use the token generated, I just get an error 'Invalid Credentials'.
Initially I started to use Passport.js, but what I found was this. In the documentation it states setup happens like so:
passport.use(new GoogleStrategy({
returnURL: 'http://www.example.com/auth/google/return',
realm: 'http://www.example.com/'
},
function(identifier, profile, done) {
User.findOrCreate({ openId: identifier }, function(err, user) {
done(err, user);
});
}));
But when I log the contents of 'identifier' it is actually something like
https://www.google.com/accounts/o8/id?id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I assume the ID is something unique to me but I can't seem to discover exactly what it is. Furthermore I don't know if it is time-limited or will last forever. As a final problem, I don't even know if I can get that same value when signing up via Android because I don't know where the value comes from. It's not the kind of API access token that I was expecting. When I output the contents of profile, it's just my name and email address - nothing that I can use for contacting the Google API with to verify the user.
The above solution I don't like anyway because it means the server hosting the client site has to make an api request in order to pass the id to the api. Or it sends the id details to the client so that it can pass them on to the api server. Or, the website server puts it into the api database, which is also a bad solution.
So next I figured I would use the javascript library from the Google sign in docs. I have something like this:
Website Client:
<script type="text/javascript">
function createUser(token)
{
$.ajax({
url:"http://api.example.com/user",
dataType: 'jsonp',
data: 'token='+token,
success:function(json){
alert("Success: "+json);
},
error:function(jqXHR, textStatus, errorThrown){
alert("Error "+textStatus+" "+errorThrown);
}
});
}
function signinCallback(authResult)
{
if(authResult['access_token'])
{
document.getElementById('signinButton').setAttribute('style', 'display: none');
alert('RES: '+JSON.stringify(authResult));
createUser(authResult['access_token']);
}
else if(authResult['error'])
{
alert('There was an error: ' + authResult['error']);
}
}
</script>
Node API user handling function:
function(req, res)
{
var callback = req.query.callback;
if(callback == null)
{
res.send("{valid:false}");
}
else
{
var token = req.query.token;
if(token == null)
{
res.send("{valid:false}");
}
else
{
var oauth2Client = new OAuth2Client('xxxxxx', 'xxxxxx', '');
oauth2Client.credentials = {
access_token: token
};
googleapis
.discover('plus', 'v1')
.execute(function(err, client){
if(client == null)
{
console.log("Client is null");
}
else
{
var request1 = client.plus.people.get({ userId: 'me' })
.withApiKey('xxxxxx');
request1.execute(function(err, result){
console.log("Result: " + (err ? err.message : result.displayName));
});
}
});
res.set('Content-Type', 'text/javascript');
res.send(callback+"({ret:'User Test'});");
}
}
}
This works fine on the client side - I see the alert box with my access token and some other details. The trouble is that when I call the google api functions on my api server for getting the user details, I get 'Invalid Credentials' returned. I assume this is because I generated the access token in javascript for a website and I'm using it from somewhere else.
So, that pretty much leaves me out of ideas. Is there an easy way to achieve this that I'm missing? I just want to be able to generate a token from a website and from an Android app that I can use on the server for validating the user's details. The generated token doesn't even need to work on the website or the Android app, just from the api server. The API server can't do the process of directing the user to Google for authorisation though because the user doesn't directly interact with it.

Resources