Refresh token for Google APIs to keep user logged in - node.js

I am developing an App on Actions on Google that uses Google Calendar. At the start of a user's first interaction, they are prompted to link their Google account for access to the calendar. But then for nearly every future interaction, the user has to authenticate again.
My code to get the access token from the user data is below. I'm not sure if there's a way to get the refresh token to add to the credentials. I've seen you should include "access_type": "offline" in the request in other answers, but I'm not sure where I'd put it in my code. Any help would be greatly appreciated!
const googleAuth = require('google-auth-library');
function getAuth(app) {
var accessToken = app.getUser().accessToken;
var clientId = CLIENT_ID;
var clientSecret = CLIENT_SECRET;
var redirectUrl = 'https://oauth-redirect.googleusercontent.com/r/my_app_name';
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
var token = {
"access_token": accessToken,
"refresh_token": "",
"access_type": "offline", //Not sure if these attributes should be here
"approval_prompt": "force",
"immediate": true,
"token_type": "Bearer"
};
oauth2Client.credentials = token;
return oauth2Client;
}
calendar.freebusy.query ({
auth: getAuth(app), //need a Google OAuth client here for API request
headers: { "content-type" : "application/json" },
resource:{
items: [
{
id: "primary"
}
],
timeMin: startDate.format(),
timeMax: endDate.format(),
timeZone: timeZoneId
}, function(err, response) {
console.log('Success!');
}
});
Not sure if this helps, but on the very first interaction a user has to select an account and agree to the calendar permission, but on subsequent interactions they just have to tap "link account to Google" and it automatically authenticates them.
EDIT:
This is a screenshot of the prompt I get on every new interaction with the app. It was my understanding that account linking is only done on the very first interaction with the app, then the accounts are linked for good and the user need not be prompted again. I am not receiving any errors, I was just wondering if and how I can modify my current code to not prompt the user to link their account on every single interaction.

Related

Can't get user google account gender and birthday fields with google People API. (Using OAuth2client)

I am implementing authentication with google in my mobile flutter app. I get the access_token in my app, and then I send it to backend which is written with Node.js. And thene I need to fetch user basic info + birthday and gender. In Google Cloud Platform console I did all configs, I added certain scopes,'https://www.googleapis.com/auth/user.birthday.read', 'https://www.googleapis.com/auth/user.gender.read',. I enabled Google People API. But I still can not get birthday and gender. Here is backend part from.
const token =
"HARDCODED_ACCESS_TOKEN";
var google = require("googleapis").google;
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2();
oauth2Client.setCredentials({
access_token: token,
scope: "https://www.googleapis.com/auth/user.gender.read",
});
var oauth2 = google.oauth2({
auth: oauth2Client,
version: "v2",
});
oauth2.userinfo.get(function (err, res) {
if (err) {
console.log(err);
} else {
console.log(res.data);
}
});
And here what I got in response.
I tried almost everything, but still couldn't get gender and birthday.
In order to get information about gender and birthdays from the authenticated user, you can call People API's people.get with resourceName=people/me and personFields=genders,birthdays:
oauth2Client.setCredentials({
access_token: token,
});
const service = google.people({version: 'v1', auth: oauth2Client});
service.people.get({
resourceName: 'people/me',
personFields: 'genders,birthdays'
}, (err, res) => {
// Do your thing
});
Notes:
You didn't provide the code for most of the authentication process, but please note that the scopes have to be provided before retrieving the access_token, since the access token depends on those scopes. Also, I'd suggest you to set a refresh_token, since the access_token will expire in an hour. For more information about the OAuth process, please take a look at the Node.js quickstart.
It is assumed that both genders and birthdays are added to the authenticated user's account.

How to know authenticated email of an user who authenticated with his google account for my app

I am integrating google authentication for my app for creating calender events.
const {google} = require('googleapis');
const SCOPES = ['https://www.googleapis.com/auth/calendar','https://www.googleapis.com/auth/userinfo.email']
I have used the above scopes in authentication and generated the url and sent to the client. Authentication got success and i am able to receive code and generating token.
But with the code, I need the authenticated user's email, whoever signed in. Is there any way/example to know how to get user's email?. I searched in google for finding solution. But i didn't get that. Help me to know about this.
After you have received the token, you need to make a HTTP request to the userinfo endpoint using that token to get the required user info.
You can get the url for that endpoint from the discovery document which can be found at https://accounts.google.com/.well-known/openid-configuration.
The current url to get user info is https://openidconnect.googleapis.com/v1/userinfo.
If you do a calendar get on the users primary calendar. All users have a primary calendar
request
GET https://www.googleapis.com/calendar/v3/calendars/primary
response
{
"kind": "calendar#calendar",
"etag": "\"JWPyaEfg9X1jMhlHCcI4tl2h6uo/AxePbI13h8-KuIOLek\"",
"id": "xxxx#gmail.com",
"summary": "xxx#gmail.com",
"timeZone": "Europe/Copenhagen",
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"eventHangout"
]
}
}
Both Id and summary will be the users email address. By doing it this way you will not need to request the https://www.googleapis.com/auth/userinfo.email scope
Node guesss
Note: I am not a node.js developer this is a wild guess you will have to work this out
function listEvents(auth) {
const calendar = google.calendar({version: 'v3', auth});
calendar.about.get({
calendarId: 'primary'
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const results = res;
if (results ) {
console.log(results.id);
} else {
console.log('No upcoming events found.');
}
});

Getting list of Google Calendar events using googleapis

Following googleapis documentation I've retrieved tokens including refresh_token:
{ access_token: 'ya29.Glv_LONG-STRING',
token_type: 'Bearer',
refresh_token: '1/7k6BLAH-BLAH-BLAH',
expiry_date: 1532141656948 }
How can I get list of Google Calendar events using this when access_token is not valid anymore, but I have the refresh_token?
You have already retrieved the refresh token. You want to retrieve an event list using Calendar API by the refresh token. If my understanding is correct, how about this sample script? In this script, it supposes the following points.
You have the refresh token which can use Calendar API.
The refresh token includes https://www.googleapis.com/auth/calendar or https://www.googleapis.com/auth/calendar.readonly for retrieving the event list using Calendar API in the scopes.
Calendar API has already been enabled at API console.
When you run the script, if the error occurs, please confirm above points.
Sample script :
const {google} = require('googleapis');
const calendar = google.calendar('v3');
var oauth2Client = new google.auth.OAuth2(
"### client ID ###",
"### client secret ###",
);
oauth2Client.setCredentials({
refresh_token: "### refresh token ###",
// access_token: "#####" // If you want to use access token, please use this.
});
calendar.events.list({
auth: oauth2Client,
calendarId: 'primary',
}, function(err, res) {
if (err) {
console.log(err);
} else {
console.log(res.data);
}
});
Note :
When you use this sample script, please set client ID, client secret, refresh token and calendarId for your environment.
I confirmed that this sample script works with the version 32.0.0 of googleapis.
Reference :
Calendar API
google-api-nodejs-client
If I misunderstand your question, I'm sorry.

How to get email and profile information from OAuth2 Google API?

I'm trying to retrieve the name of a logged in user using Google API Node.js Client, using OAuth2 API.
Following the usage example, I managed to do the login, but I can't find a way to get the profile information.
I'm not using People API nor Plus API, cause as far as i know, OAuth2 includes https://www.googleapis.com/auth/userinfo.profile, which should be enough for the task.
I have seen some similar questions and tried the solutions of this one but it didn't work, maybe it's too old (?)
With the npm package googleapis how do I get the user's email address after authenticating them?
Looking at other API's like Google Sheets, it's possible to call their functions like this:
var google = require('googleapis');
var sheets = google.sheets('v4');
...
sheets.spreadsheets.values.get({
auth: auth,
spreadsheetId: file_id,
range: my_ranges,
}, function(err, response){
...
}
);
But it seems that OAuth2 doesn't work like that...
You can use Quickstart for node.js. The detail information is https://developers.google.com/gmail/api/quickstart/nodejs. Using a sample script from Quickstart, you can retrieve access token by OAuth2, and retrieve email and user profile.
Before it runs a sample of Quickstart, please confirm Prerequisites, Step 1 and Step 2.
You can use by changing listLabels(auth) as follows. The scope is https://www.googleapis.com/auth/gmail.readonly.
Script :
var gmail = google.gmail({
auth: auth,
version: 'v1'
});
gmail.users.getProfile({
auth: auth,
userId: 'me'
}, function(err, res) {
if (err) {
console.log(err);
} else {
console.log(res);
}
});
gmail.users.messages.get({
'userId': 'me',
'id': 'mail ID',
'format': 'raw'
}, function (err, res) {
console.log(new Buffer(res.raw, 'base64').toString())
});
gmail.users.getProfile retrieves user profile.
gmail.users.messages.get retrieves email.
If I misunderstand your question, I'm sorry.
Added :
Please change above to following script. Scope is https://www.googleapis.com/auth/userinfo.profile.
Script :
var oauth2 = google.oauth2({
auth: auth,
version: 'v2'
});
oauth2.userinfo.v2.me.get(
function(err, res) {
if (err) {
console.log(err);
} else {
console.log(res);
}
});
Result :
{
id: '#####',
name: '#####',
given_name: '#####',
family_name: '#####',
link: '#####',
picture: '#####',
gender: '#####',
locale: '#####'
}
2021 Solution
This answer may divert from the originally asked question but I think it will be useful for some people who are getting google user information in the backend by generating AuthUrl and sending it to the client side and then receiving the data response in the call back URL after the user gives permission from the client side.
Some global declarations
import { google } from "googleapis";
const Oauth2Client = new google.auth.OAuth2(
googleCredentials.CLIENT_ID,
googleCredentials.CLIENT_SECRET,
googleCredentials.REDIRECT_URI
);
Generate the Auth URL with the scopes
const SCOPE = [
'https://www.googleapis.com/auth/userinfo.profile', // get user info
'https://www.googleapis.com/auth/userinfo.email', // get user email ID and if its verified or not
];
const auth_url = Oauth2Client.generateAuthUrl({
access_type: "offline",
scope: SCOPE,
prompt: "consent",
state: "GOOGLE_LOGIN",
});
return res.json({ url: auth_url }); // send the Auth URL to the front end
Get the user data in the callback
let code = req.query.code; // get the code from req, need to get access_token for the user
let { tokens } = await Oauth2Client.getToken(code); // get tokens
let oauth2Client = new google.auth.OAuth2(); // create new auth client
oauth2Client.setCredentials({access_token: tokens.access_token}); // use the new auth client with the access_token
let oauth2 = google.oauth2({
auth: oauth2Client,
version: 'v2'
});
let { data } = await oauth2.userinfo.get(); // get user info
console.log(data); // you will find name, email, picture etc. here
Feel free to discuss in the comments if there's any confusion or error
You can also look into PassportJS. They have multiple strategies, including OAuth2 and 3 different Google Auth strategies. My answer doesn't really answer your question but maybe even taking a peek at Passport's code, you may get your answer.
http://passportjs.org/

Nodejs google oAuth2 invalid credentials

I have the following piece of code that allows a user to create google calendar events. Initially it will check if the user has an access token set, if not it will initiate the regular oAuth2 authorisation process and get + save the access token and refresh token inside the user profile. The subsequent request will then allow the user to create calendar events via the access token set in his profile.
Here is the code:
function getNewToken() {
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
return authUrl;
}
module.exports = {
insert(req, res, next) {
User.findById(req.user._id, (err, user) => {
if (user.accessToken) {
oauth2Client.setCredentials({
refresh_token: user.refreshToken
});
const calendar = google.calendar('v3');
calendar.events.quickAdd({ auth: oauth2Client, calendarId: 'primary', text: 'Test Calendar' }, (err, data) => {
if (err) {
next(err);
}
res.status(200).send('Successfully added calendar event');
});
} else {
const authUrl = getNewToken();
res.redirect(authUrl);
}
});
},
googleOauth2Callback(req, res, next) {
const code = req.query.code;
oauth2Client.getToken(code, (err, token) => {
if (err) {
next(err);
}
User.findByIdAndUpdate({ _id: req.user._id }, { accessToken: token.access_token, refreshToken: token.refresh_token }, (err) => {
if (err) {
next(err);
}
});
res.send({ token });
});
}
};
However, I've noticed that after a certain time passes, I get an "401 Invalid Credentials" error. I've noticed that if I omit the access_token and just set the refresh token in the oAuth2Client then everything works as expected.
Is this the right way to handle oAuth2 tokens? Can I just continue using only the refresh token in my API requests? What happens when the refresh token expires? Any help would be appreciated.
Is this the right way to handle oAuth2 tokens?
From what I understand, yes. the tokens are short-lived and will be replaced with a new one once access has been made.
Access tokens have limited lifetimes. If your application needs access to a Google API beyond the lifetime of a single access token, it can obtain a refresh token. A refresh token allows your application to obtain new access tokens.
Can I just continue using only the refresh token in my API requests?
You can continue to use the tokens if its part of the scope.
Access tokens are valid only for the set of operations and resources described in the scope of the token request. For example, if an access token is issued for the Google+ API, it does not grant access to the Google Contacts API. You can, however, send that access token to the Google+ API multiple times for similar operations.
What happens when the refresh token expires?
Once the access tokens expire, the application uses refresh token to obtain a new one.
In general, this diagram from the Identity documentation illustrates the process fully.
For more information about oAuth2 in client-side application, check out Web Apps section of the documentation.

Resources