My Google Drive app requests the following scopes when exchanging a code for an access token:
https://www.googleapis.com/auth/drive.file
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/drive.install
In particular, this is the query string of the URL that is eventually being requested from Google during the exchange:
code=XXXXXXXXXX&grant_type=authorization_code&redirect_uri=XXXXXXXXXXX& scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file+ https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email +https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile +https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.install &client_id=XXXXXX.apps.googleusercontent.com&client_secret=XXXXXX
The response is a 400 error, with the error message "invalid_scope". What am I doing wrong?
[Edit] Additional information:
The error only happens when the user clicks through from Google Drive to create a new document. If I initiate the authentication/authorization flow from my own app, the list of scopes is accepted just fine. If the user clicks through the actual Drive app to create a new document, I get invalid_scopes.
The invalid scope is drive.install. If I remove that from the list of requested scopes when the user shows up to create a new document, things start working again. Does that make any sense at all? If the user has the Drive app installed already via us requesting that scope, why would requesting that same scope when the user shows up from the Drive app cause a problem of any kind?
I experienced a similar problem. The solution was to pass an array of scopes to the google client:
google_client.authorization.scope=[
'https://www.googleapis.com/auth/calendar.readonly',
'https://www.googleapis.com/auth/drive.appdata']
rather than a concatenated string of scopes
google_client.authorization.scope="https://www.googleapis.com/auth/calendar.readonly%2Bhttps://www.googleapis.com/auth/drive.appdata"
The GET request in the Rails log looked identical, but the result was very different!
There is a chance that You might be using tab instead of space between two auth like
https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/drive.appdata
putted on new line to show tabs
always use one white space between these two links to authorization.
this had happened with me.
The new google api (at the moment of this answer is posted) requires scope attribute to be one string and scopes separated with white space. So like this
var SCOPES = "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/gmail.send";
gapi.auth2.init({
client_id:CLIENT_ID,
scope: SCOPES
}).then (...)
You could try not escaping the + symbols. That worked for us.
Related
I'm attempting the verification process for an Apps Script web app that uses the URL Fetch service via class URLFetchApp. Per URLFetchApp documentation, this requires the scope https://www.googleapis.com/auth/script.external_request. I can't find any Google documentation for this scope, and it doesn't appear on this list of OAuth2 scopes. I'm having a hard time demonstrating how my app uses the data provided by the scope when I don't actually know what user data the scope provides. I mean, I don't think I'm using any user data... I'm just calling API executable functions from another Google Apps Script project.
What user data is this scope giving me access to? Or do I just need to explain why/how I'm using URLFetchApp?
If you head over the editor dashboard of your script, you will see further information about this scope stating :
Connect to an external service under project OAuth scopes.
Moreover, when you run your script for the first time, the permissions it is asking for are:
Create a network connection to any external service (e.g., to read or write data)
Therefore, despite not having much more description in the documentation (just in UrlFetchApp), I don't think you are using any user data apart from getting the user to use your script and connect to an external service.
I hope this has helped you. Let me know if you need anything else or if you did not understood something. :)
According the documentation if you wish to use UrlFetchApp Service then external_request is the scope you'll have to add to you manifest. Like it or not.
UrlFetch Service
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
So, I've gotten to the point in my app where I can retrieve a list of spreadsheet documents from a user's Google Drive account by using the Google Spreadsheet API. I populate the file list in a ui control that users can click on to then retrieve the list of its worksheets. It's working as expected in some cases, but in others it is not. In my request, I use the url that comes back from the file list, and even so, the API responds with:
Sorry, the file you have requested does not exist.
Make sure that you have the correct URL and that the owner of the file hasn't deleted it.
Well, surely the file wasn't deleted. I got it back in the response to my request for the files list. I also can get to the file via a normal web browser. Also, the URL is correct because that's the one the API responded with. My code does not manipulate the url that comes back in that initial files response. In fact, here is the URL that is used to grab the worksheets:
https://spreadsheets.google.com/feeds/worksheets/{long key here}/private/full
So, my question is why does my request for some worksheets come back with a response with the actual list of worksheets, but on others (which I have access to, and I know exists) I get the faulty response.
Thanks,
Arie
My app is using OAuth 2.0 and I ran into the same error with new Google Sheets. What fixed that was making a change in scope param sent during OAuth's authorize call and then reauthorising (reinitiating OAuth flow and obtaining new tokens).
Until now scope in my app was just:
https://spreadsheets.google.com/feeds
Updated scope and solution to the issue in my case:
https://spreadsheets.google.com/feeds https://docs.google.com/feeds
I'm running into this in my own stuff. At least for what I'm running into, it seems to be an issue with New Sheets. I'm sorry to not have more of a solution (I'm still trying to find out what to fix on my end) but this may help you narrow down the issue.
Code lines:
var client = new gapi.drive.share.ShareClient(key);
client.setItemIds([pathId]);
client.showSettingsDialog();
It always shows an error message:
Sorry, sharing is unavailable at this time. Please try again later
in message box.
In order to share an item, you need to authenticate your requests with a user. Usage of key doesn't work in this case. Go through the regular OAuth 2.0 flow and authorize and authenticate for a user.
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.)