In just a few months, Google will retire the old Google API Client Library and replace it with new Google Identity Services. One of the changes it brings is that some of the old approaches to dealing with Access Token expiration and maintaining the session state won’t work. Here’s how to use the new library with Oracle’s low-code platform, Oracle APEX.
A while ago, I published a detailed article on the use of the Google API Client Library for JavaScript in APEX applications: How to integrate Google Workspace with Oracle APEX and Oracle Database: The ultimate guide. This library enables integration with the Google API on the JavaScript side, allowing you to take advantage of the possibilities the Google ecosystem offers.
However, a significant change is on the horizon, which was announced on the official Google pages. The library mentioned above will soon be replaced with a new one called Google Identity Services for Web, and the old solution will be fully retired on March 31, 2023. This creates some problems for the solutions I’ve presented in my previous article, so I’ve decided to write a follow-up to address them.
In this blog post, I will present a simple example of using the new library in an APEX application along with my idea to solve the Access Token expiration problem and avoid logging in to Google every time. The main issue is that the library has been simplified to the point where – if the integration with Google is to be successful – each site refresh requires you to log in once again. As you can imagine, this isn’t very convenient, so I’ve come up with an idea to improve on what Google offers by default.
The following tutorial can be successfully implemented in existing integrations with Google, which were based on the withdrawn library.
Why API Client Library was great
But first, let’s take a step back and look at the benefits of using the Google API Client Library. I rate its usefulness very highly, mainly due to the convenience of use and built-in modules for cooperation with, for example, Google Sheets.
The library has several functionalities, including two that are very important:
- Implementation of the OAuth 2.0 authorization mechanism, which provides the current access token. It’s added in the form of a request header to those APIs that require OAuth 2.0 authorization. Most of the important APIs are authorized by OAuth 2.0, so this is a very big advantage of the library. But the most important thing is that the access token is refreshed automatically, so a valid token is at our disposal at any time.
- Maintaining session and login status in Google services. As long as Google did not force the user to log in again or the user didn’t clear their browser data, the integration with Google was almost transparent and didn’t force the user to log in on the Google side, because the session and login status was maintained. Hence, the APEX application integrated with the old library required minimal attention from the user in the context of integration with Google.
The new Google Identity Services
According to Google, the new library will bring improvements in terms of user visibility and trust, and help reduce friction during sign-in, in addition to the previously offered authentication and authorization functionalities. If you want to know more about Google Identity Services, check out the official documentation:
Google has also published instructions for migrating to the new library, describing the differences between them and how to replace the individual modules of the old library.
Description of the application
Now, I’ll show you how to avoid problems with Access Token expiration and the need to log in to Google constantly when using the new library.
I’ll use a sample application to present my approach, so let’s start with its description. There are a couple of things you need to set up in the page-level settings.
Page-level settings
In the JavaScript section, you need to perform the following steps:
- Fill in the File URLs field with the following link: https://accounts.google.com/gsi/client
- Function and Global Variable Declaration – enter the following code:
var client, accessToken; // onload function function initClient() { client = google.accounts.oauth2.initTokenClient({ client_id: 'YOUR_CLIENT_ID', scope: 'https://www.googleapis.com/auth/drive.readonly', callback: tokenReponseCallback, }); } async function tokenReponseCallback(tokenResponse) { await apex.server.process('AJAX | Save access token', {x01: tokenResponse.token_type + ' ' + tokenResponse.access_token}); // start working with API - put render function here var x = await callAPI(); } async function getCurrentToken() { var result = await apex.server.process('AJAX | Get access token'); if(result.token_exists) { accessToken = result.access_token; } else { client.requestAccessToken(); } } async function callAPI() { // add this before each promise chain, like fetching gDrive data or creating folder await getCurrentToken(); var result; try { result = await $.ajax({ headers: { 'Authorization': accessToken }, url: 'YOUR_GOOGLE_API_ENDPOINT', type: 'GET' }); console.log(result); return result; } catch(e) { if(e.readyState === 4 && e.status === 401) { client.requestAccessToken(); } } }
Here’s how it should look:
- In the initClient() function, you need to provide the correct client_id, which can be taken from the old library or a new project created within the Google API Console. For details, please refer to my previous article.
- The rest of the code shown above can remain unchanged. However, pay attention to the callAPI() function. This function is a scheme of operation. It means that, before you call any Google API, you first call getCurrentToken(). Therefore, it operates the same as in the previous library. So, if the previous token is valid, you will use it instead of applying for a new one. In the case of the new library, however, you do this openly.
- Execute when Page Loads: initClient().
The code shown above describes 4 functions that are written in the async/await approach:
- initClient() is responsible for using the library and initializing it. We provide the client ID, the scope of access to Google API, and the function that will be performed as a callback at the end.
- tokenReponseCallback() is responsible for saving the granted token to the APEX collection, from where it will be retrieved before each call of any Google API. It was also shown that from this level we can refer to the API directly, without clicking any button. This form can be useful when we need to integrate on-page load.
- getCurrentToken() is a helper function that determines whether it’s possible to use an existing token or whether you need to log in to Google services again. Here, a simple mathematical operation takes place on the date of saving the token in the collection, thanks to which you can estimate whether it’s still valid. If so, you can call Google API. If not, you have to repeat the action of obtaining a new token.
- callAPI() is a function that demonstrates how it works. First, you acquire a token (new or existing), and then you can call the API of your choice. If you encounter a 401 error, the token has probably expired, so the user will have to log in to Google again. Logging in again will guarantee you a new access token.
Overall, the above is a significant improvement and an attempt to build on what the old library offered out of the box. The new way reduces the number of annoying Google logins, which – without any improvements – comes down to having to do it every time.
In principle, the method I presented can also be used in other programming languages or platforms. It’s important to ensure that the stored data is secure.
Ajax callback
Now, let’s talk a bit about the Ajax callback function, which is used to create asynchronous web applications. To use it, you have to create a new Ajax Callback type process called “AJAX | Save access token”. Here’s the code you can use to do this:
declare c_coll_google_access_token constant varchar2(50) := 'GOOGLE_ACCESS_TOKEN'; begin apex_collection.create_collection(p_collection_name => c_coll_google_access_token, p_truncate_if_exists => 'YES'); apex_collection.add_member(p_collection_name => c_coll_google_access_token, /* save access token */ p_c001 => apex_application.g_x01, p_d001 => sysdate); apex_json.open_object; apex_json.close_object; end;
This will do the following:
- It will create an APEX collection or clean an existing one.
- It will save the new access token and SYSDATE passed as an argument. The second piece of information will allow you to calculate the validity period of the token and apply for a new token if the existing one expires.
Next, you have to create a new process “AJAX | Get access token” as an Ajax Callback process. The code is as follows:
declare c_coll_google_access_token constant varchar2(50) := 'GOOGLE_ACCESS_TOKEN'; c_seconds_in_one_day number := 24 * 60 * 60; c_token_valid_period number := 3600; l_access_token varchar2(4000); l_insert_date date; l_token_exists boolean := false; l_seconds_to_expiration number := 0; begin select c001 as access_token, d001 as insert_date into l_access_token, l_insert_date from apex_collections where collection_name = c_coll_google_access_token; l_token_exists := true; l_seconds_to_expiration := c_token_valid_period - ((sysdate - l_insert_date) * c_seconds_in_one_day); apex_json.open_object; apex_json.write('token_exists', l_token_exists); apex_json.write('access_token', l_access_token); apex_json.write('seconds_to_expiration', l_seconds_to_expiration); apex_json.close_object; exception when no_data_found then apex_json.open_object; apex_json.write('token_exists', l_token_exists); apex_json.close_object; end;
This process does the following:
- It’ll try to fetch an existing access token with its timestamp from the APEX collection.
- If the collection does not exist or is empty, it means that you don’t have any tokens obtained earlier. It’ll return false in the token_exists response, which will call the library to log back into Google services.
- If the token exists in the collection, you additionally calculate how many seconds remain until it expires and return this value together with the token in the response.
- If you want the token to be valid for the appropriate length of time, you can change the expression that calculates that time so that the token you return is always valid, e.g. for a minimum of 5 minutes.
Now, the tokens should be ready and available, like this:
The lesson I’ve learned
Your first attempts to work with asynchronous calls and control their ordering may lead to Promise() approach. In my opinion, in simple flows, when one asynchronous operation is to happen after the previous one, the Promise-based approach shouldn’t be a problem. The situation gets more complicated when your business process is a series of API calls, which you then want to send to the database, and so on. It is very easy to get nested then, and it is difficult to analyze and maintain code written in this way.
Using the async/wait approach proposed in this article is a better way. You don’t need to nest your code to tell which operation should happen next. And, finally, I also want to give you a hint on how to call your callAPI() function from the level of a dynamic action on a button in the application. Simply use the following code in the action’s identification:
(async() => { var x = await callAPI(); })();
The result should look like this:
And that’s it for this blog post. If you need more information about async/await and its advantages over Promise(), you can easily find it on the Internet. For example, here are some sources to check out:
Finally, there are many other APEX-related articles on the Pretius blog, so check them out: