I got a email from Google saying that the use of all Google+ APIs are being shut off. I currently use googleAPI.google.plus to sign people in using Google. Is this plugin going to add a update to support the new way of authorizing users with Google?
Environment details:
OS: Mac OS X
Node.js version: v 10.8.0
npm version: v6.5.0
googleapis version: 33
const googleAPI = require('googleapis');
const plus = googleAPI.google.plus({
version: 'v1',
auth: configs.googleAPIKey // specify your API key here
});
// Check if Google tokens are valid
plus.people.get({
userId: googleUserId,
fields: 'displayName,emails,name,image',
access_token: googleAccessToken
})
.then((user) => {
logger.debug('From google: ' + util.inspect(user.data));
logger.debug('First Name: ' + user.data.name.givenName);
logger.debug('Last Name: ' + user.data.name.familyName);
})
You don't show how you're using that object to do sign-in, so it is a little difficult to answer.
However, the googleapis package already supports sign-ins with an OAuth2 client that you can create with something like
const {google} = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
You can then get a URL to redirect them to so they can sign-in with something like
const url = oauth2Client.generateAuthUrl({
// If you only need one scope you can pass it as a string
scope: scopes
});
and then redirect them to url. Once they have signed into that URL, they'll be redirected to the URL you have specified as YOUR_REDIRECT_URL which will include a parameter called code. You'll need this code to exchange it for credentials, including the auth token
const {tokens} = await oauth2Client.getToken(code)
oauth2Client.setCredentials(tokens);
If you just need to use an API Key (which is what your example hints at), then you should just need to include the key the same way you do now for the API calls that you need to make. But that isn't related to authorization.
Since it looks like you want to get profile information, you can use something like userinfo or the People API and choose which fields you want for the user.
Using userinfo might look something like
oauth2client.userinfo.get().then( profile => {
// Handle profile info here
});
The people.get method gives you a little more control, and might look something like
const people = google.people({
version: "v1"
});
const fields = [
"names",
"emailAddresses",
"photos"
];
people.people.get({
resourceName: "people/me",
personFields: fields.join(',')
})
.then( user => {
// Handle the user results here
});
Related
I'm trying to use the Shopify API to query all the orders of a selected Shopify store, using the private app instead of the OAUTH method. Below I have added the code, can't seem to figure out how to get it to work cause there isn't much documentation for the use of the private apps. Does anyone know how I can achieve this or has done this before? I think I maybe wrong but there maybe an error in creating the session.
Upon running the below code I get the below error:
Error: Missing adapter implementation for 'abstractRuntimeString' - make sure to import the appropriate adapter for your platform
const { shopifyApi, ApiVersion, Session, LATEST_API_VERSION } = require('#shopify/shopify-api');
const { randomUUID } = require('crypto');
const { restResources } = require('#shopify/shopify-api/rest/admin/2022-10');
const selectedStore = {
shop: "store.myshopify.com",
api_secret: "",
api_key: "",
private_admin_key: ""
};
const shopify = shopifyApi({
apiKey: selectedStore.api_key,
apiSecretKey: selectedStore.api_secret,
scopes: ['read_orders', 'read_analytics', 'read_customers'],
hostName: '<ngrok_url>',
apiVersion: LATEST_API_VERSION,
isEmbeddedApp: false,
isPrivateApp: true,
restResources
});
const session = new Session({
id: randomUUID(),
state: 'state',
shop: selectedStore.shop,
accessToken: selectedStore.private_admin_key,
isOnline: true,
})
console.log(session)
const getOrders = async () => {
const orders = await shopify.rest.Order.all({
session,
status: "all"
})
return orders
}
getOrders()
If you have a Auth Token from a private App (inside the Shopify Admin), with permissions to read orders, you make a call to the Shopify store using that token. Look up the end point you call. Formulate your call. Make a GET or POST. Nothing hard to do there. Shopify assumes you know how to make a GET request with JS. Provide the Auth Token you gave yourself, and you'll get back all the orders you asked for. You cannot skip out on learning paging etc.. but again, that is also pretty standard stuff not special to Shopify.
I've noticed that all the node.js code samples for Google Analytics Admin and Google Analytics Data assume a service account and either a JSON file or a GOOGLE_APPLICATION_CREDENTIALS environment variable.
e.g.
const analyticsAdmin = require('#google-analytics/admin');
async function main() {
// Instantiates a client using default credentials.
// TODO(developer): uncomment and use the following line in order to
// manually set the path to the service account JSON file instead of
// using the value from the GOOGLE_APPLICATION_CREDENTIALS environment
// variable.
// const analyticsAdminClient = new analyticsAdmin.AnalyticsAdminServiceClient(
// {keyFilename: "your_key_json_file_path"});
const analyticsAdminClient = new analyticsAdmin.AnalyticsAdminServiceClient();
const [accounts] = await analyticsAdminClient.listAccounts();
console.log('Accounts:');
accounts.forEach(account => {
console.log(account);
});
}
I am building a service which allows users to use their own account to access their own data, so using a service account is not appropriate.
I initially thought I might be able to use the google-api-node-client -- Auth would be handled by building a URL to redirect and do the oauth dance...
Using google-api-nodejs-client:
const {google} = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// generate a url that asks permissions for Google Analytics scopes
const scopes = [
"https://www.googleapis.com/auth/analytics", // View and manage your Google Analytics data
"https://www.googleapis.com/auth/analytics.readonly", // View your Google Analytics data
];
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes
});
// redirect to `url` in a popup for the oauth dance
After auth, Google redirects to GET /oauthcallback?code={authorizationCode}, so we collect the code and get the token to perform subsequent OAuth2 enabled calls:
// This will provide an object with the access_token and refresh_token.
// Save these somewhere safe so they can be used at a later time.
const {tokens} = await oauth2Client.getToken(code)
oauth2Client.setCredentials(tokens);
// of course we need to handle the refresh token too
This all works fine, but is it possible to plug the OAuth2 client from the google-api-node-client code into the google-analytics-admin code?
👉 It looks like I need to somehow call analyticsAdmin.AnalyticsAdminServiceClient() with the access token I've already retrieved - but how?
The simple answer here is don't bother with the Node.js libraries for Google Analytics Admin & Google Analytics Data.
Cut out the middleman and build a very simple wrapper yourself which queries the REST APIs directly. Then you will have visibility on the whole of the process, and any errors made will be your own.
Provided you handle the refresh token correctly, this is likely all you need:
const getResponse = async (url, accessToken, options = {}) => {
const response = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response;
};
I use Python but the method could be similar. You should create a Credentials object based on the obtained token:
credentials = google.auth.credentials.Credentials(token=YOUR_TOKEN)
Then use it to create the client:
from google.analytics.admin import AnalyticsAdminServiceClient
client = AnalyticsAdminServiceClient(credentials=credentials)
client.list_account_summaries()
Stack:
Google Sign-in (Vanilla JS - client side),
Firebase Functions (ExpressJS)
Client-Side:
My Firebase function express app uses vanilla javascript on the client side. To authenticate I am making use of Firebase's Google SignIn feature client-side javascript web apps, found here.
// Firebase setup
var firebaseConfig = {
apiKey: "AIza...",
authDomain: "....firebaseapp.com",
databaseURL: "https://...-default-rtdb.firebaseio.com",
...
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
function postIdTokenToSessionLogin(idToken, csrfToken) {
return axios({
url: "/user/sessionLogin", < ----- endpoint code portion found below
method: "POST",
data: {
idToken: idToken,
csrfToken: csrfToken,
},
});
}
// ...
// On sign-in click
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth()
.signInWithPopup(provider)
.then(async value => {
const idToken = value.credential.idToken;
const csrfToken = getCookie('_csrf');
return postIdTokenToSessionLogin(idToken, csrfToken);
}).then(value => {
window.location.assign("/user/dashboard")
}).catch((error) => {
alert(error.message);
});
Note I am using value.credential.idToken (most sources imply to use this, but haven't found an example saying use this specifically)
Directly after calling signInWithPopup, a new account is created in my Firebase Console Authentication matching the gmail account that was just signed in.
Server-side:
Once I authenticate, I create an axios request passing in the {user}.credential.idToken and following the server-side setup here (ignoring the CSRF - this just doesn't want to work).
In creating the session, I use the following code in my firebase functions express app, the endpoint which is router.post('/sessionLogin', (req, res) => (part of /user route prefix):
// Set session expiration to 5 days.
const expiresIn = 60 * 60 * 24 * 5 * 1000;
const idToken = req.body.idToken.toString(); // eyJhbGciOiJSUzI1NiIsImt...[936]
admin
.auth()
.createSessionCookie(idToken, {expiresIn}) < ----------- Problem line
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
}).catch((error) => {
console.error(error);
res.status(401).send('UNAUTHORIZED REQUEST!');
});
On the createSessionCookie call, I get the following error & stack trace:
Error: There is no user record corresponding to the provided identifier.
at FirebaseAuthError.FirebaseError [as constructor] (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:44:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:90:28)
at new FirebaseAuthError (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:149:16)
at Function.FirebaseAuthError.fromServerError (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:188:16)
at C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\auth\auth-api-request.js:1570:49
at processTicksAndRejections (internal/process/task_queues.js:93:5)
This is part of the sign-in flow with a existing Gmail account.
What is causing this?
After many hours of searching, Googling - I have seen the light.
For some additional context, this error featured heavily in my struggle "Firebase ID token has invalid signature." - I will get to that in a second.
Further, another issue I also faced was using a local auth emulator for web client-side (javascript), see this for setup.
TL;DR to solve the immediate problem
Client-side remained largely the same, however the documentation provided by Firebase was inaccurate/misleading - thanks to this post, I found the solution. Thus, it follows...
Which is the ID Token? (Client-side):
The examples from here (to allow signInWithPopup), the response (if successful) results in
...
.signInWithPopup(provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
var credential = result.credential;
// This gives you a Google Access Token. You can use it to access the Google API.
var token = credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
})
Looking for an idToken, I found one using result.credential.idToken but no where on the internet on if this was infact the correct token to use.
I ran into this error using the provided idToken above:
Firebase ID token has incorrect "aud" (audience) claim. Expected
"[insert your **projectId**]" but got
"59895519979-2l78aklb7cdqlth0eob751mdm67kt301.apps.googleusercontent.com".
Make sure the ID token comes from the same Firebase project as the
service account used to authenticate this SDK.
Trying other tokens like result.credential.accessToken responded with various verification errors - what to do?
Mention earlier, this solution on Github suggested to use firebase.auth().currentUser.getIdToken() AFTER you have signed in. An example (building on my previous code) is to do the following:
...
.signInWithPopup(provider)
.then((result) => {
// current user is now valid and not null
firebase.auth().currentUser.getIdToken().then(idToken => {
// send this ID token to your server
const csrfToken = getCookie('_csrf');
return postIdTokenToSessionLogin(idToken, csrfToken);
})
})
At this point, you can verify your token and createSessionCookies to your heart's desire.
BUT, a secondary issue I unknowingly created for myself using the Authentication Emulator.
To setup for client-side use:
var auth = firebase.auth();
auth.useEmulator("http://localhost:9099");
To setup for hosting your firebase functions app (assuming you are using this with e.g. nodejs + express, see this for setup, ask in comments, can provide more details if needed)
Using Authentication Emulator caused the following errors AFTER using the above mentioned "fix". Thus, DO NOT RUN the local authentication emulator (with Google sign-in of a valid Google account) as you will consistently get.
Firebase ID token has invalid signature. See
https://firebase.google.com/docs/auth/admin/verify-id-tokens for
details on how to retrieve an ID token
You can use all your local emulators, but (so far in my experience) you will need to use an online authenticator.
I have Google Sign-in working on my app: the relevant code is roughly:
var acc = await signInService.signIn();
var auth = await acc.authentication;
var token = auth.idToken;
This gives me a nice long token, which I then pass to my backend with an HTTP POST (this is working fine), and then try to verify. I have the same google-services.json file in my flutter tree and on the backend server (which is nodejs/restify). The backend code is roughly:
let creds = require('./google-services.json');
let auth = require('google-auth-library').OAuth2Client;
let client = new auth(creds.client[0].oauth_client[0].client_id);
. . .
let ticket = await client.verifyIdToken({
idToken: token,
audience: creds.client[0].oauth_client[0].client_id
});
let payload = ticket.getPayload();
This consistently returns my the error "Wrong recipient, payload audience != requiredAudience".
I have also tried registering separately with GCP console and using those keys/client_id instead, but same result. Where can I find the valid client_id that will properly verify this token?
The problem here is the client_id that is being used to create an OAuth2Client and the client_id being used as the audience in the verifyIdToken is the same. The client_id for the audience should be the client_id that was used in your frontend application to get the id_token.
Below is sample code from Google documentation.
const {OAuth2Client} = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: token,
audience: CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
// If request specified a G Suite domain:
//const domain = payload['hd'];
}
verify().catch(console.error);
And here is the link for the documentation.
Hope this helps.
Another quick solution might be change the name of your param "audience" to "requiredAudience". It works to me. If you copied the code from google, maybe the google documentation is outdated.
client.verifyIdToken({
idToken,
requiredAudience: GOOGLE_CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
It has already been mentioned above that requiredAudience works instead of audience, but I noticed requiredAudience works for both {client_id : <CLIENT_ID>} and <CLIENT_ID>. So maybe you were referencing creds.client[0].oauth_client[0] instead of creds.client[0].oauth_client[0].client_id? I have not been able to find any docs on the difference between requiredAudience and audience, however make sure you are sending just the <CLIENT_ID> instead of {client_id : <CLIENT_ID>}.
Google doc: link
verifyIdToken()'s call signature doesn't require the audience parameter. That's also stated in the changelog. So you can skip it, and it'll work. The documentation is kinda misleading on that.
It's also the reason why using requiredAudience works because it actually isn't being used by the method, so it's the same as not providing it.
I've been faceing this issue with google-auth-library version 8.7.0 and came across a workaround only if you have a single CLIENT_ID to verify.
Once you create your OAuth2Client like this:
const googleClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
You don't need to pass the CLIENT_ID in verifyIdToken function as it uses your googleClient object to create auth url.
Following this documentation I succeed to perform Google Sign-In for server-side apps and have access to user's GoogleCalendar using Python on server side. I fail to do that with NodeJS.
Just in a nutshell - with Python I used the auth_code I've sent from the browser and got the credentials just like that:
from oauth2client import client
credentials = client.credentials_from_clientsecrets_and_code(
CLIENT_SECRET_FILE,
['https://www.googleapis.com/auth/drive.appdata', 'profile', 'email'],
auth_code)
Then I could store in DB the value of:
gc_credentials_json = credentials.to_json()
And generate credentials (yes it uses the refresh_token alone when it's needed):
client.Credentials.new_from_json(gc_credentials_json)
So I want to do the same using NodeJS:
easily generate credentials using just: CLIENT_SECRET_FILE, scopes and auth_code (just like I did with Python)
receive credentials using previous credentials value without analysing if the access token is expired - I prefer a ready (well tested by the community) solution
Thank you in advance!
I've implemented it using google-auth-library package.
Here is the function to retrieve the gcClient:
const performAuth = async () => {
const tokens = await parseTokenFromDB();
const auth = new OAuth2Client(
downloadedCredentialsJson.web.client_id,
downloadedCredentialsJson.web.client_secret
);
auth.on('tokens', async (newTokens) => {
updateDBWithNewTokens(newTokens);
});
await auth.setCredentials(tokens);
const gcClient = google.calendar({version: 'v3', auth});
return gcClient;
};
Here is the template for parseTokenFromCurrentDB function just to give the idea of its output:
const parseTokenFromCurrentDB = async () => {
// Put here your code to retrieve from DB the values below
return {
access_token,
token_type,
refresh_token,
expiry_date,
};
};
So using this implementation one can get gcClient:
const gcClient = await gc.getGcClient(org);
and use its methods, e.g.:
const gcInfo = await gc.getInfo(gcClient);
const events = await gc.getEvents(gcClient, calcPeriodInGcFormat());