Lately I've been noticing a strange behavior when trying to access Drive specific actions. The use case is as follows:
the user installs the Chrome Store application
the user launches the application, we get a refresh token for the email and the profile scopes
the user wants to export a document into Google Drive
error message 403: appNotInstalled is returned
If the user goes to Google Drive and opens a file from there using our application, they are redirected to a new authorization dialog that asks for the email, profile, drive scopes. After the user grants access, the initial export also works fine. My assumption is that the initial refresh token that we had was no longer valid, even though exchanging it for an access token worked and the refresh tokens don't expire.
Shouldn't we receive a more descriptive error message in this case that would suggest that we simply have to redirect the user to the authentication dialog instead of the Chrome Store listing?
The issue might be that you are using a different client id/secret than the one you registered for the Drive SDK.
A quick test would be to:
revoke all granted tokens for your application on your test account
visit your application from the New Tab Page, it should redirect you to the authorization page: copy the URL you generated (do not approve).
visit your application from Drive, it should redirect you to the authorization page: copy the URL (do not approve).
Make sure the 2 URLs are identical (they should be), especially look for similarities in the scope and client_id query parameters.
Related
Background:
This is about using a Gmail Addon created using Google App Script.
When the user installs the addon, there is the OAuth Consent Screen where user provides his consent to allow the "Product name shown to users" (as configured in the OAuth Screen) to allow the access specified.
Now, I read : https://developers.google.com/identity/protocols/CrossClientAuth
which states :
When a user grants access to your app for a particular scope, the user
is looking at the user consent screen, which includes project-level
product branding that you set up in the Google API Console. (For
information about setting up the consent screen, see Setting up OAuth
2.0 in the API Console help.) Therefore, Google considers that when a user has granted access to a particular scope to any client ID in a
project, the grant indicates the user's trust in the whole application
for that scope.
Now, I have a server web component (a lambda) (belonging to the same product) that needs access to the user's email same access that the user provided after installing the addon ("Authorized Access" button).
Question(s) :
Is there a way to have cross-client (a backend server and a gmail addon) in my case to have the backend to just get access to user's data without triggering additional (basically whatever the user has provided consent to)?
Note: Using an additional authorization screen triggered manually using the GAS OAuth library I was able to get the "Auth Code" which I pass to the server using which the server now has access to the consented data (we have used the same client id and secret). However, the problem with this approach is :
User gets 2 emails about the permissions granted. Addon and Manually triggered flow.
User has to authorize the gmail addons for first access and then another which I trigger manually.
Even if there was a way I could get the "Auth Code" when the user installs the addon that would also do.
Apologize in advance there is a lot of scattered documentation and though I went through many it is likely I may have missed something.
We only issue one authorization code (refresh token) in exchange for one user authorization/approval. Your app can get new access tokens on android or web without a user approval. But if it needs a refresh token again, user still need to approve the request.
So if the addon can talk to your server, you could give it a short lived access token or will need to user authorization.
I have create script backed by a Google Spreadsheet in Google Drive and published it as a web app, setting "Execute the app as: me" and "Who has access to the app: Anyone, even anonymous". The main page serves html content from the spreadsheet.
The basic functionalities are: a "random internet user" is able to see some informations, while an user logged with his Google Account is able to submit information as well.
The issues is - if I set the permission to "anyone" people are first redirected to the standard google login page, and once logged in have to accept the script permissions. Once that's done, navigating to the web app will give the "logged in user" version as long as you are logged in with your google account.
However - if the web app is set to allow "anonymous users" there doesn't seem to be any (at least obvious way) to allow anonymous users to login and accept the app permissions.
What I have tried until now:
a link to the url google redirects me to if I am not logged on if the web app require the user to be logged in. That works, but I feel it is not the right way and also, once logged in, there is no way to logout or to switch account; it seems that once you are logged in, you are logged in forever
to use https://developers.google.com/apps-script/reference/script/authorization-info#getAuthorizationUrl() to get the auth url - but I don't know how to use the url it returns (navigating the user to that page in the web app frame on the top frame both result in a blank page)
How can I get a login (and possibly a switch login) url to allow anonymous users to login in my web app?
Created sample code for 2 .gs web app projects:
one - no auth, runs as owner, anonymous can access
two - needs auth, runs as user, anyone can access
...as I have no idea what your "I got it to work redirecting the user to the URL" statement looks like in code.
The 1st file has a button with a click listener to act as the login button, but it's just calling for a window.top.location.href change.
The 2nd app is using ScriptApp.invalidateAuth(); to log users out after being called from a client-side button click.
Demo - 1st web app url
I'd still vote to just offer a 2nd web app as a link that pops open in a new window though and as I note in the comment, the setup above forces users to re-authorize each time and after the signout.
I am integrating ASP.NET application using Google Drive API. For this after authentication we re uploading Files to Google drive. I am using Google client library to Call the APIs.
Everything is working as expected I am able to authenticate user successfully and able to upload the file successfully.
In one scenario when the user Google account is suspended then I am getting refresh token from Google but my upload method is failing and it is not uploading the file to Google drive.
I want to restrict the user on Signup screen itself, when account is suspended.
What parameter do I have to pass to achieve this please suggest?
Unfortunately this info is not easily available. You have two options :
Use the Directory API to see if the user is suspended. This requires additional OAuth permissions to be provided by an admin of the domain.
At login, try and perform a Drive API call to see if you get an error or not. If you get an error (with a couple of retried) and the error message matches the one you had for suspended users, then you can deny access to the user.
To get a token, I am calling:
GoogleAuthUtil.getToken(context,account, "oauth2:" +"https://www.googleapis.com/auth/drive.appdata");
Which will always give me a token, so my code works, BUT I am expecting(at least the first time) to get a UserRecoverableAuthException so I can use that intent on the exception to prompt the user for action. The action in this case is granting my app access to that user's Google drive storage.
From the web site, I disconnect the app from the drive, run my app, files get written to my gdrive, and I see, from the website, that the app is connected to my drive.
Why is the user not getting prompted to allow access?
If user has already given permissions for you app for that scope, no UserRecoverableAuthException will be thrown.
I discovered in my case the Authorization dialog wasn't been showed because I added singleInstance to the activity in the AndroidManifest.xml
Removing from the AndroidManifest
android:launchMode = "singleInstance"
fixed this problem and a couple of other strange transition effects between activities.
The first time you connect your app to drive it asks for permission by itself when you send the account name.
The Exception you are expecting is only thrown if the user Disconnects the app.
Try disconnecting the app and then read/write to drive. It should throw this Exception.
I've created a Google App Script that handle 2 different OAuth connections.
1- Google itself to send mail on behalf of the user and access google docs (google api console used to get keys, secret)
2- gtraxapp wich is a timesheet cloud-based app. (Script is registered, got a key/secret, etc.)
The script is published as a web app. It works perfectly for my user.
When logged on a different user name, I can authorize Google OAuth without providing different key/secret, and emails will be sent from the actual user.
Problem happens with the 2nd app (gTrax).
Authorization seems to work. Running the function inside the script to authorize lead to a screen asking for permission, gtrax then appears in the account as a registered app (could revoke access if needed).
But, when running the app, I get a message saying I need permission to do this action (UrlFetchApp / simple get)
My question is :
Is this possible that I need to register each user to get a key/secret for everyone (and dealing with that in the script)...
Or do OAuth can be registered with 1 key/secret ?
In other word, are (should) key/secret linked to a single user or are they only a kind of RSA-like key pairs that, when verified, can be used to authorize any user.
My understanding is this. When you use built-in Apps Script functions, like MailApp.sendEmail, the Google Apps Script "environment" takes care for you to ask authorization for the user (1st time he access your app) and save and manage the oAuth tokens for you, so it all runs smoothly.
When you call an external service using UrlFetchApp, Apps Script oAuth authorization process works differently. The authorization is just a strange popup you get on the script editor, when you actually make the fetch call. It is not processed at "compile time" and asked before you run anything like the other services. But you also do this step only once.
The "gotcha" is that this different authorization process does not work when a user is running the app as a webapp. AFAIK it only works from the script editor itself or running directly from a spreadsheet.
If your users are just a known few, you could advise everybody to open the script editor (or a spreadsheet that contains it) and run an specific function that will just attempt the UrlFetchApp.fetch call so the popup shows up and they authorize it. Once this step is done, they can use the webapp normally. Apps Script will do the magic for you after that.
But if you plan to share this broadly, say at the Chrome Web Store, and don't want to ask every user to do this somewhat strange step, then you'll need to manage all the authorization process yourself. It means, you'll have to register your app with the third party service (if it's Google's, it's at the API Console), where you will receive a client id and a client secret. With those you'll have to place a "Authorize" submit button on your app html that will redirect the users to the 3rd party authorization url, providing the correct scope, etc. When they authorize it, the 3rd party will redirect the user back to your app providing a code token as URL parameter. You'll use this code to call the 3rd party oAuth service to get the real access and possibly refresh tokens that you'll have to use on your UrlFetch calls. You'll be responsible to save these tokens, refresh them when they expire and so on. Not a very simple procedure :-/
Oh, and although your app have only one id and secret, the tokens are per user. Which makes sense, since each call you do must be on behalf of a specific user and he *must* have authorized it.
I hope this helps.