Google OAuth - Incremental scope and security

Hello everyone,

I’m trying to set up and understand the different ways to make an OAuth connection with Google and access the user’s various services (Drive, Calendar, etc.) on a Bubble application.

I can identify 3 ways of making this OAuth connection to Google : a simple URL redirect with the Open an external website action (very manual) ; using the API Connector with OAuth2 User-Agent-Flow Authentication (the most advanced); the Google Plugin (a simple encapsulation of the API Connector’s OAuth 2 to retrieve the user’s info).

After a lot of research and testing, I’m left with two main questions:

  • How to increment access rights (scope) with API Connector in OAuth2 after a simple registration?
  • When connecting with the “Open an external website” action (without using the API Connector), we’re forced to manage token generation and token refresh ourselves. But is it really safe to receive the code in the URL parameter of the Google connection return, use it to generate the token, and use this token in the header to make calls to Google services?

Detail problem 1:

Login/Signup with API Connector with Authentication in OAuth2 User-Agent-Flow, works very well, here is its configuration :

As recommended in the Google documentation, the rights requested at registration are the lowest possible, only the user’s important information. Then, incrementally, our Bubble application must request access to services as needed: Drive, Agenda, Gmail, etc.

The scopes required for the first configuration are as follows:

https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile
openid

As far as I understand the system with API Connector and OAuth2 User-Agent-Flow Authentication in Bubble, it handles code retrieval, token generation and refresh itself, but completely invisibly. The generated token is associated with the access rights (scopes) of the services accepted by the user in the Google authentication window. But because the authentication window uses the scopes configured during OAuth initialization and these scopes can’t be dynamic, I can’t open a Google authentication window with new scopes, to have a token associated with the user’s Drive or Calendar services… (everything works if I set all the scopes, but that’s not the objective with incremental authentication).

The problem is that I can’t find any API Connector configuration to make the scope dynamic or modify the token saved in the current user, Bubble handles it invisibly. What’s more, you shouldn’t create and use two Google OAuth systems, as Bubble will return an error from the existing user if you try to connect to the same Google account with each of the systems. All API calls to Google services associated with the logged-in user must therefore be made under a single OAuth2 configuration.

Alternative solution :

The solution I’ve found is to combine the OAuth2 connection in API Connector (used for simple Login/Signup), with the very manual solution of using an “Open an external website” action. This allows me to manage the token associated with the new scopes myself, and to use this token as a header for each of my service-specific calls (Drive, Agenda, etc.).

Which brings me to the second problem.

Detail problem 2 :

The OAuth connection to Google can also be made without the API Connector with OAuth2 User-Agent-Flow Authentication, using a simple “Open an external website” redirect action to the Google authentication window and the correct URL parameters.

Here’s the action and URL used to authorize the connection to Google Drive :

https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=https://ottho-oauth.bubbleapps.io/version-test/googleconnect&response_type=code&access_type=offline&include_granted_scopes=true&prompt=consent&client_id=40573060...ahpsc.apps.googleusercontent.com&scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/docs https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.photos.readonly https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/drive.install https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.apps.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.metadata https://www.googleapis.com/auth/drive.metadata.readonly

If I understand the parameters of this URL correctly, described in the Google documentation :

  • https://accounts.google.com/o/oauth2/v2/auth : Base of the URL to the Google authentication window.
  • redirect_uri : Redirect link to which the user will be redirected once the Google connection has been made (I don’t have the feeling that the URL https://ottho-oauth.bubbleapps.io/api/1.1/oauth_redirect provided in the OAuth 2 config of the API Connector works here).
  • response_type : can have the value code or token to retrieve respectively the code or the token in the return URL parameter after the Google connection.
  • access_type=offline : mainly used to receive the “refresh_token” in return for token generation (Google doc). (refresh_token is required to generate a new token when the current one has expired).
  • include_granted_scopes=true: Enables applications to use incremental authorization by adding rights to the connected user.
  • prompt=consent : Can also be equal to none or select_account and allows user consent to be requested.
  • client_id : Key identifying our application, retrieved from the Google Console.
  • scope : Add all the scopes the user needs to access Google Drive, Calendar, etc. (There are probably too many scopes in my configuration, they’re just for testing purposes and should be selected according to the application’s needs).

Google Drive connection steps (after a simple Google OAuth2 API Connector registration) :

  1. Redirect the user to the URL detailed above, with all the scopes needed to connect to the service.

  2. Retrieve “code” as URL parameter from user’s return after validating new accesses.

  3. Modification of the token code using the API call https://oauth2.googleapis.com/token. (The response to this call also gives the refresh_token, useful in a second call when the token has expired).

  4. bis. Call to refresh the token using the refresh_token retrieved during token generation (same endpoint as for code generation, but with different parameters).

  5. Retrieve files from the user’s Google Drive using the token added to the call header

Everything works fine with this method. I’d just like to validate with the community and security experts, to be sure that this method is secure and optimized.

My questions:

  1. Is retrieving the URL code to transform it into a token secure? Can’t there be a “man in the middle” to retrieve the code?
  2. Wouldn’t it be better/possible to have a single token managed by Bubble’s OAuth that evolves according to validated rights?
  3. If I manage it manually for each of the services, I need to store the token, the refresh_token and the expiry date (1H) in the DB to authorize calls to the services and refresh the token when it expires. But how would you refresh the token? The expiry says 1H, but I have the feeling that it may be valid for several days to authenticate calls, so doing a refresh every hour seems absurd. Would it be better to retrieve the error on the front end and refresh it only if the return of a call gives us an expired token error?

Last question:

Do you think my subject is too long? (Sorry, I wanted to make sure I gave you all the information you needed to understand my problem). :crazy_face:

Thank you for your answers

4 Likes