I'm trying to get a list of reviews of my Google business through the API to display them on my website. But I can't figure out how to authenticate the API server side. The documentation only mentions OAuth2.0 authentication from the client side with redirect URLs, but there won't be a client going to a confirmation page in this case.
I just want to be able to perform this request in Node.js:
GET https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/reviews
I've already submitted the application and been approved for a Business API and enabled the various APIs in my account. I have created OAuth2.0 credentials. I'm just not sure how to move forward from here.
How can I authenticate Google Business API requests on the server?
I ended up putting together an answer through lots of searching. Google documentation is all over the place.
A basic example of getting reviews is below. But to get it to work there are a few steps first.
You'll need to add a Service Account in your API credentials page
The name, ID, and description aren't particularly important. Just something that makes sense to you
Go to the service account details -> keys -> add key -> create new key -> JSON
This will download a key file to your computer. Keep it private.
Grant domain wide delegation for your service account
To do this, you'll need to be an admin of the account if you're part of an organisation
It will ask for a Client ID, but it is called Unique ID in the service account details. They're the same thing.
Add whatever scopes you need for the services you want to access. For reviews, the scope listed in the example below is enough.
The subject field in google.auth.JWT needs to be an admin of the account. I used my own email.
That should be it! You should now be able to fill out the values in the example below and access the API from a server. Other services may require different scopes.
You can get account and location info from the API docs. The endpoints and data formats are fairly well documented. Just authentication isn't very well explained it seems.
import axios from 'axios';
import {google} from 'googleapis';
import key from './key.json' assert {type: 'json'};
main();
async function main(){
const reviews=await getReviews();
}
async function getReviews(){
const token=await authenticate();
const accountId='YOUR ACCOUNT ID';
const locationId='YOUR LOCATION ID';
const url=`https://mybusiness.googleapis.com/v4/accounts/`+
`${accountId}/locations/${locationId}/reviews`;
const resp=await axios.get(url, {
headers: {
authorization: `Bearer ${token}`
}
});
return resp.data.reviews;
}
async function authenticate(){
const scopes=[
'https://www.googleapis.com/auth/business.manage'
];
const jwt=new google.auth.JWT({
email: key.client_email,
key: key.private_key,
subject: 'ADMIN EMAIL',
scopes
});
const resp=await jwt.authorize();
return resp.access_token.replace(/\.{2,}/g, '');
}
Related
I have added google cloud service account in a project and its working. But the problem is that after an hour(i think), I get this error:
The API returned an error: TypeError: source.hasOwnProperty is not a function
Internal Server Error
and I need to restart the application to make it work.
Here in this StackOverflow post, I found this:
Once you get an access token it is treated in the same way - and is
expected to expire after 1 hour, at which time a new access token will
need to be requested, which for a service account means creating and
signing a new assertion.
but didn't help.
I'm using Node js and amazon secret service:
the code I have used to authorize:
const jwtClient = new google.auth.JWT(
client_email,
null,
private_key,
scopes
);
jwtClient.authorize((authErr) =>{
if(authErr){
const deferred = q.defer();
deferred.reject(new Error('Google drive authentication error, !'));
}
});
Any idea?
hint: Is there any policy in AWS secret to access a secret or in google cloud to access a service account? for example access in local or online?
[NOTE: You are using a service account to access Google Drive. A service account will have its own Google Drive. Is this your intention or is your goal to share your Google Drive with the service account?]
Is there any policy in AWS secret to access a secret or in google
cloud to access a service account? for example access in local or
online?
I am not sure what you are asking. AWS has IAM policies to control secret management. Since you are able to create a Signed JWT from stored secrets, I will assume that this is not an issue. Google does not have policies regarding accessing service accounts - if you have the service account JSON key material, you can do whatever the service account is authorized to do until the service account is deleted, modified, etc.
Now on to the real issue.
Your Signed JWT has expired and you need to create a new one. You need to track the lifetime of tokens that you create and recreate/refresh the tokens before they expire. The default expiration in Google's world is 3,600 seconds. Since you are creating your own token, there is no "wrapper" code around your token to handle expiration.
The error that you are getting is caused by a code crash. Since you did not include your code, I cannot tell you where. However, the solution is to catch errors so that expiration exceptions can be managed.
I recommend instead of creating the Google Drive Client using a Signed JWT that you create the client with a service account. Token expiration and refresh will be managed for you.
Very few Google services still support Signed JWTs (which your code is using). You should switch to using service accounts, which start off with a Signed JWT and then exchange that for an OAuth 2.0 Access Token internally.
There are several libraries that you can use. Either of the following will provide the features that you should be using instead of crafting your own Signed JWTs.
https://github.com/googleapis/google-auth-library-nodejs
https://github.com/googleapis/google-api-nodejs-client
The following code is an "example" and is not meant to be tested and debugged. Change the scopes in this example to match what you require. Remove the section where I load a service-account.json file and replace with your AWS Secrets code. Fill out the code with your required functionality. If you have a problem, create a new question with the code that you wrote and detailed error messages.
const {GoogleAuth} = require('google-auth-library');
const {google} = require('googleapis');
const key = require('service-account.json');
/**
* Instead of specifying the type of client you'd like to use (JWT, OAuth2, etc)
* this library will automatically choose the right client based on the environment.
*/
async function main() {
const auth = new GoogleAuth({
credentials: {
client_email: key.client_email,
private_key: key.private_key,
},
scopes: 'https://www.googleapis.com/auth/drive.metadata.readonly'
});
const drive = google.drive('v3');
// List Drive files.
drive.files.list({ auth: auth }, (listErr, resp) => {
if (listErr) {
console.log(listErr);
return;
}
resp.data.files.forEach((file) => {
console.log(`${file.name} (${file.mimeType})`);
});
});
}
main()
I would like to use Auth0 to gain access for Google APIs. Was trying to follow the instructions on this post
Currently to trigger Auth0, I used conv.ask(new SignIn()); and app.intent("actions_intent_SIGN_IN", (conv, params, signin) => { ... })
I get a token from const code = conv.user.access.token; in my actions_intent_SIGN_IN.
However, it seems that this token isn't the token that is used to gain an access_token for Google, aka thru POST /oauth/token. Whatever this token is, it seems like it doesn't work for any of the requests except GET /userinfo. On Google Cloud Functions, I get this on my log:
{ error: 'invalid_grant', error_description: 'Invalid authorization code' }
I played around for a bit with Postman and managed to retrieve the (seemingly?) correct authorization code that can be used for POST /oauth/token thru GET /authorize and building my own url like https://[APP NAME].auth0.com/authorize?response_type=code&client_id=[CLIENT ID]&redirect_uri=[REDIRECT URI]. The authorization code appeared in https://[APP NAME].auth0.com/login/callback?code=[CODE HERE]
The issue is - how do I retrieve the code=[CODE HERE] from dialogflow?
You can authenticate with service accounts that use OAuth2.0 -- you can read more about that in general here and they'll save you a network call. You'll also need to find the appropriate Google API scope(s) here. Dialogflow has samples that show how this is done on Github, for example.
const {google} = require('googleapis');
const serviceAccount = { }; // Enter Service Account private key JSON
const client = new google.auth.JWT({
email: serviceAccount.client_email,
key: serviceAccount.private_key,
scopes: ['https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive']
});
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'.
I am creating an Android/iOS app which communicates with a Node.js server and would like to identify them securely on my server using Google (and/or Facebook) and OAuth2. I've looked at the following documentation: https://developers.google.com/+/web/signin/server-side-flow
I do not need authorization, I only need authentication (I only want to make sure that the person calling my Node.js service is the person they say they are). To achieve this, if I understand properly, I have to let the user log in using Google on the client side, this will give them an authorization_code which they can then give to my server. My server can then exchange that code for an access_token, and therefore retrieve information about the user. I am then guaranteed that the user is the person they say they are.
The Google documentations (link above) says: "In the Authorized redirect URI field, delete the default value. It is not used for this case.", however, for my server to exchange the authorization_code for an access_token, it needs to provide a redirect_uri, am I missing something?
The redirect_uri is useless for Unity games, for instance (since logging in with Google simply opens a new "window", which is closed when logged in, no redirection involved).
TL;DR
How do you use OAuth2 to authenticate users between my client and my server without redirection?
TL;DR How do you use OAuth2 to authenticate users between my client and my server without redirection?
You can't. OAuth requires that the user is directed to an authorization (and possibly login) screen, and then redirected back to your app.
EDIT 20/12/22. See comment below regarding latest status
Have you looked at this documentation? https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi
Choosing a redirect URI
When you create a client ID in the Google Developers Console, two
redirect_uris are created for you: urn:ietf:wg:oauth:2.0:oob and
http://localhost. The value your application uses determines how the
authorization code is returned to your application.
http://localhost
This value signals to the Google Authorization Server that the
authorization code should be returned as a query string parameter to
the web server on the client. You may specify a port number without
changing the Google Developers Console configuration. To receive the
authorization code using this URL, your application must be listening
on the local web server. This is possible on many, but not all,
platforms. If your platform supports it, this is the recommended
mechanism for obtaining the authorization code.
I had this problem and it took me ages to find the "postmessage" solution that Nepoxx mentions in the comments of the accepted answer here.
For clarification, here's what worked for me.
Follow steps 1-6 here: https://developers.google.com/identity/sign-in/web/server-side-flow
Install googleapis library npm install --save googleapis
For the server-side token exchange do this:
var googleapis = require('googleapis');
var OAuth2 = googleapis.auth.OAuth2;
var oauth2Client = new OAuth2(
GOOGLE_SSO_CLIENT_ID,
GOOGLE_SSO_CLIENT_SECRET,
'postmessage' // this is where you might otherwise specifiy a redirect_uri
);
oauth2Client.getToken(CODE_FROM_STEP_5_OF_INSTRUCTIONS, function(err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
});
The redirect_uri can be a URL with a custom URL scheme for which the client registered a handler. This is described here: What's a redirect URI? how does it apply to iOS app for OAuth2.0?. It is not so much about "redirecting" it is about a callback endpoint to your app.
And it become really easy if you use VueJS with https://github.com/guruahn/vue-google-oauth2
Client side
import GAuth from 'vue-google-oauth2'
Vue.use(GAuth, {
clientId: 'xxxxxxx.apps.googleusercontent.com',
scope: 'profile',
})
async signWithGoogle() {
const code = await this.$gAuth.getAuthCode() //
console.log(code ) // { code: 'x/xxxxxxxxxx' }
// send the code to your auth server
// and retrieve a JWT or something to keep in localstorage
// to send on every request and compare with database
}
Server side
import { google } from 'googleapis'
const oauth2Client = new google.auth.OAuth2(GOOGLE_ID, GOOGLE_SECRET, 'postmessage')
google.options({ auth: oauth2Client })
async function getAccount(code) {
// the code you sent with the client
const { tokens } = await oauth2Client.getToken(code)
oauth2Client.setCredentials(tokens)
const oauth2 = google.oauth2({ version: 'v2' })
const { data: { id } } = await oauth2.userinfo.get()
// there you have the id of the user to store it in the database
// and send it back in a JWT
}
I have an iOS which uses OAuth and OAuth2 providers (Facebook, google, twitter, etc) to validate a user and provide access tokens. Apart from minimal data such as name and email address, the app doesn't uses these services for anything except authentication.
The app then sends the access token to a server to indicate that the user is authenticated.
The server is written in Node.js and before doing anything it needs to validate the supplied access token against the correct OAuth* service.
I've been looking around, but so far all the node.js authentication modules I've found appear to be for logging in and authenticating through web pages supplied by the server.
Does anyone know any node.js modules that can do simple validation of a supplied access token?
To the best of my knowledge (and as far as I can tell from reading the specifications) the OAuth and OAuth 2 specs do not specify a single endpoint for access token validation. That means you will need custom code for each of the providers to validate an access token only.
I looked up what to do for the endpoints you specified:
Facebook
It seems others have used the graph API's 'me' endpoint for Facebook to check if the token is valid. Basically, request:
https://graph.facebook.com/me?access_token={accessToken}
Google
Google have a dedicated debugging endpoint for getting access token information, with nice documentation, too. Basically, request:
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={accessToken}
However, they recommend that you don't do this for production:
The tokeninfo endpoint is useful for debugging but for production
purposes, retrieve Google's public keys from the keys endpoint and
perform the validation locally. You should retrieve the keys URI from
the Discovery document using the jwks_uri metadata value. Requests to
the debugging endpoint may be throttled or otherwise subject to
intermittent errors.
Since Google changes its public keys only infrequently, you can cache
them using the cache directives of the HTTP response and, in the vast
majority of cases, perform local validation much more efficiently than
by using the tokeninfo endpoint. This validation requires retrieving
and parsing certificates, and making the appropriate cryptographic
calls to check the signature. Fortunately, there are well-debugged
libraries available in a wide variety of languages to accomplish this
(see jwt.io).
Twitter
Twitter doesn't seem to have a really obvious way to do this. I would suspect that because the account settings data is pretty static, that might be the best way of verifying (fetching tweets would presumably have a higher latency?), so you can request (with the appropriate OAuth signature etc.):
https://api.twitter.com/1.1/account/settings.json
Note that this API is rate-limited to 15 times per window.
All in all this seems trickier than it would first appear. It might be a better idea to implement some kind of session/auth support on the server. Basically, you could verify the external OAuth token you get once, and then assign the user some session token of your own with which you authenticate with the user ID (email, FB id, whatever) on your own server, rather than continuing to make requests to the OAuth providers for every request you get yourself.
For google in production, install google-auth-library (npm install google-auth-library --save) and use the following:
const { OAuth2Client } = require('google-auth-library');
const client = new OAuth2Client(GOOGLE_CLIENT_ID); // Replace by your client ID
async function verifyGoogleToken(token) {
const ticket = await client.verifyIdToken({
idToken: token,
audience: GOOGLE_CLIENT_ID // Replace by your client ID
});
const payload = ticket.getPayload();
return payload;
}
router.post("/auth/google", (req, res, next) => {
verifyGoogleToken(req.body.idToken).then(user => {
console.log(user); // Token is valid, do whatever you want with the user
})
.catch(console.error); // Token invalid
});
More info on Authenticate google token with a backend server, examples for node.js, java, python and php can be found.
For Facebook, do an https request like:
const https = require('https');
router.post("/auth/facebook", (req, res, next) => {
const options = {
hostname: 'graph.facebook.com',
port: 443,
path: '/me?access_token=' + req.body.authToken,
method: 'GET'
}
const request = https.get(options, response => {
response.on('data', function (user) {
user = JSON.parse(user.toString());
console.log(user);
});
})
request.on('error', (message) => {
console.error(message);
});
request.end();
})
In production for google you can use:
https://www.npmjs.com/package/google-auth-library
const ticket = client.verifyIdToken({
idToken: ctx.request.body.idToken,
audience: process.env.GOOGLE_CLIENTID
})
To get info about token from Google use, be careful vith version api
https://www.googleapis.com/oauth2/v3/tokeninfo?access_token={accessToken}