If you're using a basic Google Sign-In integration in your Node.js application, you might be already using google-auth-library. The challenge comes when you want to add Google APIs while keeping this integration to manage the OAuth 2.0 flow and token lifecycle. This goal requires the usage of another library: googleapis.
This small tutorial shows how you can use both libraries, google-auth-library and googleapis, so that users can have this workflow:
- Authenticate with Google Sign-In.
- Incremental authorization for the new scope: Authorize your application to use their information to integrate it with a Google API. For instance, Google Calendar.
If you want to use Google services on behalf of a user when the user is offline, you should use a server-side flow as described by Google's documentation:
To use Google services on behalf of a user when the user is offline, you must use a hybrid server-side flow where a user authorizes your app on the client-side using the JavaScript API client and you send a special one-time authorization code to your server. Your server exchanges this one-time-use code to acquire its own access and refresh tokens from Google for the server to be able to make its own API calls, which can be done while the user is offline.
For this, your client-side JavaScript for authentication might require some changes to request offline access:
$('#signinButton').click(function() {
auth2.grantOfflineAccess().then(signInCallback);
});
In the response, you will have a JSON object with an authorization code:
{"code":"4/yU4cQZTMnnMtetyFcIWNItG32eKxxxgXXX-Z4yyJJJo.4qHskT-UtugceFc0ZRONyF4z7U4UmAI"}
After this, you can use the one-time code to exchange it for an access token and refresh token. Here are some workflow details:
The code is your one-time code that your server can exchange for its own access token and refresh token. You can only obtain a refresh token after the user has been presented an authorization dialog requesting offline access. If you've specified the select-account prompt in the OfflineAccessOptions [...], you must store the refresh token that you retrieve for later use because subsequent exchanges will return null for the refresh token
Therefore, you should use google-auth-library to complete this workflow in the back-end. For this, you'll use the authentication code to get a refresh token. However, as this is an offline workflow, you also need to verify the integrity of the provided code as the documentation explains:
If you use Google Sign-In with an app or site that communicates with a backend server, you might need to identify the currently signed-in user on the server. To do so securely, after a user successfully signs in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity of the ID token and use the user information contained in the token
The final function to get the refresh token that you should persist in your database might look like this:
const { OAuth2Client } = require('google-auth-library');
/**
* Create a new OAuth2Client, and go through the OAuth2 content
* workflow. Return the refresh token.
*/
function getRefreshToken(code, scope) {
return new Promise((resolve, reject) => {
// Create an oAuth client to authorize the API call. Secrets should be
// downloaded from the Google Developers Console.
const oAuth2Client = new OAuth2Client(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// Generate the url that will be used for the consent dialog.
const authorizeUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope,
});
// Verify the integrity of the idToken through the authentication
// code and use the user information contained in the token
const { tokens } = await client.getToken(code);
const ticket = await client.verifyIdToken({
idToken: tokens.id_token!,
audience: keys.web.client_secret,
});
idInfo = ticket.getPayload();
return tokens.refresh_token;
})
}
With this refresh token, you can create a Google API's client with the googleapis library anytime. You'll see how to complete this workflow in the next section.
At this point, we've refactored the authentication workflow to support Google APIs. However, you haven't asked the user to authorize it yet. Here you have to follow another piece of documentation to implement incremental authorization:
When requesting user permission to access user data or other resources, you can request all scopes up-front in the initial request or request scopes only as needed, using incremental authorization. Using incremental authorization, your app initially requests only the scopes required to start your app, then requests additional scopes as new permissions are required, in a context that identifies the reason for the request to the user.
Since you also need to grant offline access, you should request additional permissions through your client-side application. Keep in mind that you already need an active session.
const googleOauth = gapi.auth2.getAuthInstance();
const newScope = "https://www.googleapis.com/auth/calendar"
googleOauth = auth2.currentUser.get();
googleOauth.grantOfflineAccess({ scope: newScope }).then(
function(success){
console.log(JSON.stringify({ message: "success", value: success }));
},
function(fail){
alert(JSON.stringify({message: "fail", value: fail}));
});
Congratulations! You're done with the front-end changes and you're only missing one step. To create a Google API's client in the back-end with the googleapis library, you need to use the refresh token from the previous step:
const { google } = require('googleapis');
// Create an oAuth client to authorize the API call. Secrets should be
// downloaded from the Google Developers Console.
const oauth2Client = new google.auth.OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
client.setCredentials({ refresh_token: refreshToken });
That's it! You can use this client to integrate any Google API with your application. For instance, here is an example with Google Calendar:
calendar.events.insert({
// Client from previous step
auth: client,
calendarId: 'primary',
resource: {
'summary': 'Quito Lambda',
'location': 'Quito, Ecuador',
// ...
},
}, function(err, event) {
if (err) {
console.log('There was an error contacting the Calendar service: ' + err);
return;
}
console.log('Event created: %s', event.htmlLink);
});
The goal of this tutorial is to provide code snippets to facilitate the integration between Google API and your Node.js application. I know that it can be a bit confusing because you have to use several pieces of documentation as a reference in order to come up with this workflow, so I hope this helps!