Handling tokens in Google Drive - google-drive-api

I went through the Quickstart on how to upload a file to Google Drive (for Android) and everything works fine. However, it isn't clear whether I am responsible for storing tokens and handling exceptions if they expire. Does the SDK code used in the Quickstart handle this for me behind the scenes?:
https://developers.google.com/drive/quickstart-android
If I regularly call this code (taken from the Quickstart):
credential = GoogleAccountCredential.usingOAuth2(this, DriveScopes.DRIVE);
credential.setSelectedAccountName(accountName);
service = getDriveService(credential);
and then call some drive method, will it eventually generate an exception when the token expires or does the SDK code catch this internally and automatically attempt to retrieve a refreshed token?
To be even more specific, am I required to implement the code shown here:
https://developers.google.com/drive/credentials
What also isn't clear to me is the difference between an access token and refresh token. Then there is "short lived" tokens and "long lived" tokens. Kind of confusing.

On Android, when you use Google Play Services, all of the work is handled for you, including getting the token and refreshing it. This is explained in the quickstart guide that you have linked, and there is nothing more that you need to do.

Related

"Invalid legacy scope provided" error when attempting to access a Google API from web app?

Here's what I'm trying to do:
Remotely execute a Google Apps Script function from an online automation service (Zoho Flow, similar in purpose to Zapier.)*
This function that I am remotely executing is to add gmail labels to everyone in an organization.
In order to do this, I've done the following:
Created a service account with domain-wide-delegation in the same GCP project associated with the function that adds gmail labels.
Used the OAuth2 library for Google Apps Script to perpetuate JWT authorization for that service account in my script.
Verified that function works with service account functionality for all users in a Google oragnization within google apps script
Deployed this script as an API Executable to enable remote execution
Created authtokens authorized with the mail scope for the Apps Script API (https://mail.google.com/), as well as an additional scope required by the OAuth2 library in my script. This is done in the google developer playground, using OAuth2.0 Client ID credentials that are authorized as a part of that same GCP project.
Successfully executed the function remotely using the Google Apps Script API with Postman
Now, in order to actually make this functionality official, I now have to replace postman and the google developer console with the actual client, which is this Zoho Flow platform for api connections and process automation.
In order to make the switch I've done the following;
Added the authorized redirect URl for the client (Zoho Flow) under the same credentials that are used for the Google Developer Playground:
Utilized the credentials found from the JSON representation of those credentials** for creating what's called a "connection" in this software, which is basically an easy-to-use OAuth2.0 connection with a nice front-end that can be used for accessing API data (this is the part where I create this connection):
Entered the same scope for this connection that were authorized in the google developer playground, as well as that additional scope (https://www.googleapis.com/auth/script.external_request) that the OAuth2.0 library for Google Apps Script uses:
Lo and behold, I get the error below when attempting to gain access to Google API data from a web application, and the scopes that are said to cause the error is every scope except for the mail scope: https://mail.google.com/. I've done additional testing to verify that every other scope that is a part of the google apps script API also create this error, not just this external scope for the OAuth2 library.
Support said it was likely an issue with my credential permissions; is my line of logic for this correct? If it is, then it is an issue with their software that I will have them pursue further.
Some additional testing has been done on the software (Zoho Flow) to confirm whether or not it is a software error or not: I'm not certain if the process defined in my answer is best practices, or even 100% correct (other users can speak to it,) but I've confirmed that the error I am seeing is likely an error with the client.
Here's why:
When authorizing for multiple scopes, I get the error seen at the end of my question:
When changing that connection to utilize only one of my scopes instead, like so:
The connection proceeds properly, and I can remotely execute functions that utilize only that one scope successfully. This does work for any one scope that I authorize. The only problem is, it's a rare occurrence that a script only requires one scope. While this I can get around this major bug, I cannot remotely execute my function that adds gmail labels to all users in an organization because it requires two scopes. This will be mentioned to the development team and it should be fixed soon.

How to debug server-side `redirect_uri_mismatch` error from Google Signin

I'm attempting to use Google Sign-In with a firebase function following Google's documentation for Google Sign-In for server-side apps, however I'm running into a redirect_uri_mismatch error (unlike the examples in that documentation, I am using Google's nodejs SDK).
The general flow I'm following is to use google's javascript SDK to request an offlineAccessCode() from a user, send that access code to the server (firebase function), use the access code to get an access token and refresh token, and save the refresh token for later use (this is the flow outlined in the documentation).
It might seem like the meaning of the term redirect_uri_mismatch is self-explanatory, but, as stated in the linked documentation, The Authorized redirect URI field does not require a value. Redirect URIs are not used with JavaScript APIs. That line comes from the section "Step 1: Create a client ID and client secret" and is explaining how to configure the OAuth client credentials. This is the only time in the documentation that a redirect uri is mentioned, and there is no indication that a redirect URI is needed when the server uses the authorization code to fetch an access token.
Indeed, the concept of a redirect uri doesn't seem to make sense in the context of a cloud function using an access code obtained from a client. If the server needs to supply a matching redirect_uri as the javascript client (which doesn't seem to be specified anywhere), I'm not sure what redirect_uri the javascript client uses.
So with that background, does anyone have any idea how to solve this redirect_uri_mismatch error?
Or, more generally, any pointers on implementing this auth flow (my cloud function is written in javascript using the google nodejs sdk). I'm finding it very hard to debug this issue.
Update
I realized that the reason why the error message says redirect_uri_mismatch is because I had been testing various values as redirect_uri. When I remove the (optional) redirect_uri param from the request, the response from google's servers is invalid_request: Missing parameter redirect_uri. So google's making it clear that it wants a redirect URI, even as the documentation seems to imply that it isn't necessary for this auth flow. As a larger problem, the documentation doesn't seem to describe how to set up a redirect URI for an auth flow on a single page app.
Ah HA! The answer (as given in this other S.O. answer) is to set the redirect_uri to "postmessage" on the server (firebase function in my case). I have no idea where this answer came from (i.e. I haven't been able to find it in the docs), but it works.

How to fetch Google OAuth access tokens from Chrome extensions - tradeoffs between options

EDIT: In case it helps others who find this, the fact that Gaia clears cookies after Chrome restart is known behavior. They recommend the use of Chrome login. See more here.
The core question: how do I fetch Google OAuth access tokens in the background from a Chrome extension without using the getAuthToken method in the chrome.identity API? This method forces users to login to Chrome, which isn't ideal for our app. There's also no way I can find to retrieve OpenID Connect ID tokens using the native methods exposed in the identity API.
Currently, I'm fetching tokens using the launchWebAuthFlow method from the chrome.identity API. This method launches an "Identity API Scope Approval UI" window in which the user makes the initial OAuth grant. This window (referred to as "gaia") has its own session / cookie data, separate from a browser session.
After the initial OAuth grant, the extension periodically retrieves access tokens in the background, and subsequently fetches data from Google APIs (e.g. Drive). This works well. When a user quits and re-opens Chrome, however, fetching access tokens fails - gaia loses its session cookies when Chrome restarts, so running the launchWebFlowAuth method doesn't complete because Google can't authenticate the user (unless we prompt the user to login in the Identity API Scope Approval UI again). Others seem to have had the same issue.
Google Sign-In would be ideal, but it doesn't work in Chrome extensions (I'm getting the same "Invalid cookiePolicy" error as the author of that issue).
Any advice on how to best handle this use case would be very much appreciated!

In Google Apps Script, avoid second 'Review Permissions' prompt, possibly by using approval_prompt

I have searched StackOverflow, etc. for a solution to this problem, and several answers 'point me in a direction' (mentioning approval_prompt = auto not approval_prompt = force), but none are applicable (as far as I can tell) to my situation.
I have a Web Application hosted at www.mjpanel.com that expects to use a Google Apps Script that I 'own', but the Javascript at www.mjpanel.com calls the Google Apps Script (deployed as a Web App with doGet()) as a Web Service. It expects the web service call to return various JSON objects.
If the user has not yet authorized my application, the call to the Google Apps Script Web App / Web Service will not return a JSON object, causing www.mjpanel.com Javascript code to fail saying "Invalid Request" (because it isn't a JSON object as my code expects).
To prevent this from happening, www.mjpanel.com uses gapi.auth2.init to get the permissions/scopes it needs. I'm developing everything now, so if/whenever the Google Apps Script evolve to use something (like sending GMail emails as the user) that is new, I have been figuring out the scope to request, adding it to the list of scopes in the gapi.auth2.init call, and everything is fine. The next time a user uses the app., they get initially prompted for the newly added scope, then everything proceeds fine.
However, now sometimes one of my test users has a Web Service call fail because Google Apps Script is returning another request for permissions for a 'new permission' of 'Have Offline Access'.
There's nothing about my script that would warrant the user needing to grant this permission.
When I research, a lot of stuff (mostly about requesting OAuth2 stuff in a 'structure' different than the way my app. is set up) says it has to do with submitting a 'approval_prompt=force' in my request URL.
However, the way I have my app set up, all the URLs I would use (aside from my 'custom stuff' in the query string) are dictated by Google Apps Script. And I can't find any place where any URL I use has an approval_prompt in it.
I can't figure out where I would need to configure that approval_prompt to be auto (as is recommended in the 'successful answers' I find).
Of if the idea of approval_prompt is 'on the wrong track', any information in general to help me solve this problem would be greatly appreciated.
Thanks in advance for any help you can give me.
Unfortunately the Google Apps Script native OAuth flow includes approval_prompt=force. This causes the following conditions:
If cookie exists in browser and has permission for this application the consent screen will not be displayed.
If cookie exists in browser but does not have permission for this application: consent screen will be displayed
If cookie does not exist in browser and the application has permissions: application will request 'Have offline access'.
The easiest way around this is to manage your own OAuth flow and use the Execution API. The following link will take you to the javascript quick start.
https://developers.google.com/apps-script/guides/rest/quickstart/target-script

How to authorize with oauth 2.0 from appscript to Google APIs?

I'm playing around with AppScript and try to get an oAuth 2.0 access token.
Any sample out there how to get this working in AppScript?
I am working on a cleaner tutorialized version of this, but here is a simple Gist that should give you some sample code on how things would work -
https://gist.github.com/4079885
It still lacks logout, error handling and the refresh_token capability, but at least you should be able to log in and call a oAuth 2 protected Google API (in this case its a profile API).
You can see it in action here -
https://script.google.com/macros/s/AKfycby3gHf7vlIsfOOa9C27z9kVE79DybcuJHtEnNZqT5G8LumszQG3/exec
The key is to use oAuth 2 Web Server flow. Take a look at getAndStoreAccessToken function in the gist to get the key details.
I hope to have this published in the next few weeks but hopefully this will help in the mean time.
UPDATE - adding in info on redirect_uri
The client secret is tied to specific redirect URIs that the authorization code is returned to.
You need to set that at - https://code.google.com/apis/console/
The highlighted URI needs to match the published URI (ends in /exec). You get the published URI from the script editor under Publish -> Deploy as web app. Make sure you are saving new versions and publishing the new versions when you make changes (the published URI stays the same).
I've modified the example above to use the newish state token API and the CacheService instead of UserProperties, which is now deprecated. Using the state token API seems to make things a little more secure, as the callback url will stop accepting a state token after a timeout.
The same caveats apply. Your redirect URIs have to be added to your (script) project in the developer's console, meanwhile you have to yank the CLIENT_SECRET and CLIENT_ID from the console and paste them in. If you're working within a domain, there don't seem to be any guarantees on what URL will be returned by ScriptApp.getService().getUrl(), so I wound up basically having it get the address dynamically, then waiting for to fail on the the (second) redirect, and then hard-coded the resulting URI.
https://gist.github.com/mclaughta/2f4af6f14d6aeadb7611
Note that you can build an OAuth2 flow using this new API, but it's not a complete sample yet:
https://developers.google.com/apps-script/reference/script/script-app#newStateToken()
In particular, you should not pass 'state' directly to the /usercallback URL yourself, because the OAuth2 service provider is responsible for round-tripping the 'state' parameter. (Instead, you pass 'state' to the auth URL, and the service provider automatically attaches it to the callback URL.)