I'm using Google Play Android Developer API to server to server check subscription status of our users' subscriptions but after successful authorization and asking for an existing subscription I get the 401 response with the following message 'The current user has insufficient permissions to perform the requsted operation'.
Visiting https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=XXXXXX I can see that I do have the requested scope (https://www.googleapis.com/auth/androidpublisher) but I still get the same response everytime.
Did anyone else have the same problem?
Edit: I've seen what the Explore API app does, it adds the key in the query string of a request but I don't have that value. In the console I've created a Service Account Client Id which has a client id, email address and a private key but there is no API key which apparently Explore API uses.
Edit no. 2: I've added the service account generated email both to Google Play Developer Console and Google Wallet console but I still have no acces. I'm using nodejs and the google-oauth-jwt because there is not google provided lib for nodejs.
Here is the code I'm using to make a request:
var request = require('google-oauth-jwt').requestWithJWT();
function makeReq() {
request({
url: 'https://www.googleapis.com/androidpublisher/v1.1/applications/{packageName}/subscriptions/{subscriptionId}/purchases/{purchaseToken}',
jwt: {
// use the email address of the service account, as seen in the API console
email: 'blahblahtrutjtrutj#developer.gserviceaccount.com',
// use the PEM file we generated from the downloaded key
keyFile: 'purchases-test.pem',
// specify the scopes you wish to access
scopes: ['https://www.googleapis.com/auth/androidpublisher']
}
}, function (err, res, body) {
if (err) {
console.log(err);
} else {
console.log("BODY IS ------------------------------------------");
console.log(JSON.parse(body));
}
});
}
If your app is only released in a closed alpha track, you'll also have to add your service account's email address (client_email) to the License Testers at Settings -> Account detail in the Play Console.
There is an email address associated with your service account.
This needs to have appropriate permissions in both the dev console AND the Play store. Make sure to add the service address to the Play store.
The way I approached it was to use
var googleAuth = require('google-oauth-jwt'),
authObject = {
email: 'blahblahtrutjtrutj#developer.gserviceaccount.com',
keyFile: 'purchases-test.pem',
scopes: ['https://www.googleapis.com/auth/androidpublisher']
};
googleAuth.authenticate(authObject, function (err, token) {
next(err, token);
});
I store the token in redis for an hour and use that token to make my request to the store:
var opts = {
url : verifyUrl + payload.packageName + '/inapp/' + payload.productId + '/purchases/' + payload.token,
headers: {
authorization : 'Bearer ' + token
}
};
request.get(opts, function (error, response, body) {
next(error, response, body);
});
Related
I have configured azure AD service and an user in it and used react-adal to get the token, which worked fine.But now i need to change this flow and instead have my own login form and send the credentials to node.js express server and verify these from azure ad without a login popup from azure and store the returned token in the passport session.I have tried using node-adal but not sure how this can be achieved ,Can this be done? Are there any examples for this.Thanks
Agree with #juunas, here is the non-interactive method(login with username and password) for your reference. This sample is used to manage MicrosoftGraph Resources.
var msRestAzure = require('ms-rest-azure');
var graphRbacManagementClient = require('azure-graph');
var tenantId='abcd-efgh-ijk-lmno-12345';
// Enter your tenant ID here which can be found from your Azure AD URL
// Eg. https://manage.windowsazure.com/example.com#Workspaces/ActiveDirectoryExtension/Directory/<TenantId>/users
msRestAzure.loginWithUsernamePassword('username#contosocorp.onmicrosoft.com', 'your-password', { tokenAudience: 'graph', domain: tenantId }, function (err, credentials, subscriptions) {
if (err) console.log(err);
var client = new graphRbacManagementClient(credentials, tenantId);
var userParams = {
accountEnabled: true,
userPrincipalName: 'OfficialStark#<yourdomain.com>', //please add your domain over here
displayName: 'Jon Snow',
mailNickname: 'OfficialStark',
passwordProfile: {
password: 'WinterisComing!',
forceChangePasswordNextLogin: false
}
};
client.users.create(userParams, function (err, user, request, response) {
if (err) return console.log(err);
console.log(user);
var userObjectId = user.objectId;
client.users.list(function (err, result, request, response) {
if (err) return console.log(err);
console.log(result);
client.users.deleteMethod(userObjectId, function (err, result, request, response) {
if (err) return console.log(err);
console.log(result);
});
});
});
});
There is a way, but you should not use it.
When using federated authentication, you should not be handling passwords.
The one way that works is a legacy migration path, named the ROPC flow.
However, none of these will work:
User with MFA
User synced from on-prem AD
User with expired password
If you want to do something as the user in the background, have them login and store their refresh token securely.
You'd need to exchange the front-end access token for a back-end access token + refresh token.
Then you can use the refresh token to get new tokens for the user whenever.
Alternatively you can require application permissions to APIs and access them using client credentials from your API (client id + secret/certificate).
so i am using this: https://github.com/google/google-api-nodejs-client#using-api-keys
here is the nodejs code:
router.get("/deals", function(req, res, next) {
var key = require('../gmail-a574e06ad196.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
['https://www.googleapis.com/auth/gmail.readonly', 'https://mail.google.com/'], // an array of auth scopes
null
);
jwtClient.authorize(function (err, tokens) {
if (err) {
res.send('123');
return;
}
console.log('token is: ', tokens)
// Make an authorized request to list Drive files.
drive.users.labels.list({
auth: jwtClient,
userId: 'me'
}, function (err, resp) {
console.log(err)
res.send('123')
});
});
});
but once i hit that api, i got the following error:
errors:
[ { domain: 'global',
reason: 'failedPrecondition',
message: 'Bad Request' } ] }
I google around. found on some sites that said, the service account only work if you have G-Suite account, which is a paid account. My gmail account is a normal personal account. so no matter what i do, it just wont work?
Is that true?
What i am trying to do is, so i have a gmail account to collects newsletters, I want to create a nodejs api that returns/lists all emails from that account. I dont want oAuth, because really, no need to manually login. All i want is when page load, login automaically happens and the api returns list of emails, so everyone can see the list. Is there any other appoach of achieving this?
Thanks
you can not use service account with a normal users Gmail account due to the fact that there is no way to delicate access to another user. you can only do it with gsuite.
you could consider going through the SMTP or IMAP server's directly
I have a Native Client Application setup in my Azure Active Directory environment. I am trying to write a Node app for Utility purposes to interact with the Azure Management APIs. My challenge is just authenticating my app. At this time, I have:
let azure = {
clientId: '[only-for-my-eyes]',
key: '[only-for-my-eyes]',
tenantDomain: 'mydomain.onmicrosoft.com',
tenantId: '[only-for-my-eyes]'
};
let authenticationRequest = {
url: `https://login.microsoftonline.com/${azure.tenantDomain}/oauth2/v2.0/authorize`,
headers: {
'Content-Type':'application/x-www-form-urlencoded'
},
formData: {
response_type: 'code',
response_mode: 'form_post',
grant_type:'client_credentials',
resource: 'https://management.azure.com',
client_id: azure.clientId,
client_secret: azure.key
}
};
request.post(authenticationRequest, function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body);
} else {
console.log(response.statusCode);
console.log(response.statusMessage);
}
});
When the above runs, the 200 status code block is executed. But, it just prints out a bunch of HTML. If I'm looking at it correctly, it looks like the HTML of the login screen. I'm trying to get an access token that I can pass to the management APIs.
What am I missing?
Why not just use ARMClient? All the nasty token business is taken care of.
From https://www.npmjs.com/package/armclient:
Initialization:
// ES5
var ArmClient = require('armclient');
var client = ArmClient({
subscriptionId: '111111-2222-3333333',
auth: ArmClient.clientCredentials({
tenantId: '444444-555555-666666666',
clientId: '777777-888888-999999999',
clientSecret: 'aaaabbbbbccccc' // or servicePrincipalPassword
})
});
Get resources in your subscription:
client.get('https://management.azure.com/subscriptions/111-222-333-444/resourceGroups/lab/providers/Microsoft.Automation/automationAccounts', { 'api-version': '2015-10-31' })
.then((res) => {
console.log(res.body);
console.log(res.headers);
})
.catch((err) => {
console.log(err);
});
I believe that particular endpoint is intended for a GET with those given parameters, not a POST. I suspect what you're seeing is probably just the generic error message:
Sorry, but we’re having trouble signing you in.
We received a bad request.
What you are trying to do is to call the authorization page with a POST request. You don't have to send a POST (or GET) request here, you must redirect your user to that authorization URL.
In addition, you must have a redirect URI (I don't see it in your azure object). This redirect URI is a callback to your application. For the rest of my answer, let say it is stored in azure.redirectUri
let url = 'https://login.microsoftonline.com/${azure.tenantDomain}/oauth2/v2.0/authorize?response_type=code&response_mode=form_post&client_id={azureclient_id}&resource=https%3A%2F%2Fmanagement.azure.com&redirect_uri={azure.redirectUri}'
response.writeHead(302, {
'Location': url
});
response.end();
The user will be redirected to the authorization page and will have to accept (or deny) your application request. Then the user is redirected back to your Node.js application (azure.redirectUri).
As your response_mode is form_post, if the user accepted your application request, you will receive the authorization code in the body parameters.
With that code your application will be able to get an access token by calling the token endpoint.
So I am working on a Node.js app that will poll Google Analytics api ever x amount of seconds. I have setup a "Service Account", and converted the p12 key to a .pem file. Initial setup looks something like this:
var authClient = new google.auth.JWT(authData.serviceEmail, authData.keyFile, null, authData.scope, '');
authClient.authorize(function(err, tokens) {
if (err) {
winston.log('error', 'Error authorizing with GA', {error: err});
return;
}
setInterval(function() {
analytics.data.realtime.get({
'auth': authClient,
...
}, function (err, body) {
// getting 401 error here
})
}, 20000);
});
I had not realized that the initial tokens have an expiration date of 1 hour; however the tokens I receive look like this:
{
access_token: ...,
token_type: 'Bearer',
expiry_date: NaN,
refresh_token: 'jwt-placeholder
}
My question is, once I get that 401 invalidCredentials error, do I simply just re-authorize to get a new access token to be able to poll from Google Analytics? I am new to JWT, and this seems like it will be authorizing way too many times. Is there a limit to this?
For reference, I am using the Google API Node.js Client Library
Yes, just rebuild authClient as you did the first time. Service Accounts don't have refresh tokens like other OAuth flows, you just rebuild the authentication when the current access token expires.
I am trying to upload videos from server without any manual authentication by user in the client side . I tried the below code snippet for video upload but it authenticates the user in the browser and asks for the acceptance of the app.
var ResumableUpload = require('node-youtube-resumable-upload');
var googleauth = require('google-auth-cli');
var google = require('googleapis');
var getTokens = function(callback) {
googleauth({
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/youtube.upload' //can do just 'youtube', but 'youtube.upload' is more restrictive
},
{ client_id: CLIENT_ID, //replace with your client_id and _secret
client_secret: CLIENT_SECRET,
port: 3000
},
function(err, authClient, tokens) {
console.log(tokens);
callback(tokens);
});
};
getTokens(function(result) {
tokens = result;
upload();
});
var upload = function() {
var metadata = {snippet: { title: 'title', description: 'Uploaded with ResumableUpload' },
status: { privacyStatus: 'public' }};
var resumableUpload = new ResumableUpload(); //create new ResumableUpload
resumableUpload.tokens = tokens;
resumableUpload.filepath = 'youtube/test4.mp4';
resumableUpload.metadata = metadata;
resumableUpload.monitor = true;
resumableUpload.eventEmitter.on('progress', function(progress) {
console.log(progress);
});
resumableUpload.initUpload(function(result) {
console.log(result);
return;
});
}
But for my app it should directly upload the video to youtube from the server. For that I need the access token and refresh token I tried lot to get the access token directly but I couldn't able to get it.
So any help or idea to how to make the video upload directly from server to a channel account. I searched lot in google for a node module to do that but I couldn't able to find it.
I have been using this approach to upload video
Getting the web based generated token using the client library.
Getting the youtube upload permission from user for my application &
access_type=offline.
Access type offline gives refresh token in response. This token
will help to continue upload from backend server token when its
expires.
After getting the permission. It will redirect to URL with code.
Using the given code generate access_token
Save this token for future use.
Use the same token to push the video from your server to youtube
server
Refresh the token when it expires.
But is there any way to implement this approach without getting the youtube upload permission from user for my application.
You can do server side authetication using google API(JWT) with "Service Account". But direct upload from your server to youtube server without user permission is not possible. For uploading the video google needs OAuth2.0 authentication. It will give you error unAuthorized(401)- youtubeSignupRequired with "Service Account" using JWT authentication.
Becuase of the above limitation. You have use below Approach to work with this is-
Get the web based generated token using the client library.
Get the youtube upload permission from user for your application & access_type=offline.
Access type offline gives you refresh token in response. This token will help you to continue upload from backend server token when its expires.
After getting the permission. It will redirect to URL with code.
Using the given code generate access_token
Save this token for future use.
Use the same token to push the video from your server to youtube server
Refresh the token when it expires. And follow the step 3 - 5 again.
Currently this is the only way to upload the video on youtube.
Added the code on git repository nodejs-upload-youtube-video-using-google-api
For why its not possible? Check the below reference link & code:
From google API Doc: This error is commonly seen if you try to use the OAuth 2.0 Service Account flow. YouTube does not support Service Accounts, and if you attempt to authenticate using a Service Account, you will get this error. You can check all the error code & its detail using link: YouTube Data API - Errors
From gadata Issues: Youtube v3 Google Service Account Access
From google blog spot:List of Google API supported using Service Account
Check below code to get access_token from server side
You can check it yourself using below steps & code:
Go to Google Developer Console
Create Project
To Get Google+ API Access go to: APIs & Auth->APIs ->enable YouTube Data API v3
To Enable Service Account go to: APIs & Auth->Credentials->Create new Client ID->Click on Service Account->Create Client Id
Save the secret file on your system & keep it secure.
Create the secret key using below command & file you have downloaded:
openssl pkcs12 -in /home/rajesh/Downloads/Yourkeyfile.p12 -out youtube.pem -nodes
- Enter password: ***notasecret***
6. You can authorize & access api from server side as below:
var google = require('googleapis');
var authClient = new google.auth.JWT(
'Service account client email address', #You will get "Email address" in developer console for Service Account:
'youtube.pem', #path to pem file which we create using step 6
null,
['https://www.googleapis.com/auth/youtube.upload'],
null
);
authClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
return;
}
console.log(tokens);
});
Get youtube video list using Service Account(working):
var google = require('googleapis');
var youtube = google.youtube('v3');
var authClient = new google.auth.JWT(
'Service account client email address', #You will get "Email address" in developer console for Service Account:
'youtube.pem',
null,
['https://www.googleapis.com/auth/youtube','https://www.googleapis.com/auth/youtube.upload'],
null
);
authClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
return;
}
youtube.videos.list({auth:authClient,part:'snippet',chart:'mostPopular'}, function(err, resp) {
console.log(resp);
console.log(err);
});
});
Insert youtube video using Service Account and googleapis module:
var google = require('googleapis');
var youtube = google.youtube('v3');
var authClient = new google.auth.JWT(
'Service account client email address', #You will get "Email address" in developer console for Service Account:
'youtube.pem',
null,
['https://www.googleapis.com/auth/youtube','https://www.googleapis.com/auth/youtube.upload'],
null
);
authClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
return;
}
youtube.videos.insert({auth:authClient,part:'snippet,status,contentDetails'},function(err,resp)
console.log(resp);
console.log(err);
});
});
Insert/Upload API Returned below Error:
{ errors:
[ { domain: 'youtube.header',
reason: 'youtubeSignupRequired',
message: 'Unauthorized',
locationType: 'header',
location: 'Authorization' } ],
code: 401,
message: 'Unauthorized' }
Insert youtube video using Service Account and ResumableUpload module:
var google = require('googleapis');
var ResumableUpload = require('node-youtube-resumable-upload');
var authClient = new google.auth.JWT(
'Service account client email address', #You will get "Email address" in developer console for Service Account:
'youtube.pem',
null,
['https://www.googleapis.com/auth/youtube','https://www.googleapis.com/auth/youtube.upload'],
null
);
authClient.authorize(function(err, tokens) {
if (err) {
console.log(err);
return;
}
var metadata = {snippet: { title: 'title', description: 'Uploaded with ResumableUpload' },status: { privacyStatus: 'private' }};
var resumableUpload = new ResumableUpload(); //create new ResumableUpload
resumableUpload.tokens = tokens;
resumableUpload.filepath = 'youtube.3gp';
resumableUpload.metadata = metadata;
resumableUpload.monitor = true;
resumableUpload.eventEmitter.on('progress', function(progress) {
console.log(progress);
});
resumableUpload.initUpload(function(result) {
console.log(result);
return;
});
});
Insert/Upload API Returned below Error:
{ 'www-authenticate': 'Bearer realm="https://accounts.google.com/AuthSubRequest", error=invalid_token',
'content-type': 'application/json; charset=UTF-8',
'content-length': '255',
date: 'Tue, 16 Sep 2014 10:21:53 GMT',
server: 'UploadServer ("Built on Aug 18 2014 11:58:36 (1408388316)")',
'alternate-protocol': '443:quic,p=0.002' }
Screen shot attached for "How to get google key?"
Conclusion: Uploading a video without user permission is not possible.