0

I want to reuse the OAuth2 client-secret of a Google Apps script project to access Google APIs on behalf of this script (e.g. via Sheets API reading a spreadsheet where the script has access). Users with a Google account granted the necessary scopes to the script. Now I'd like to replace the script with a new app without asking the users again for user consent. Typically, when the script (or the app) runs the users would be offline.

My first question would be, if this scenario is a supported OAuth2 use-case with Google API authorization, and if so, what would be the best way to implement it, especially to prevent security issues?

Client secrets of the script

  1. OAuth2 client-secret file of the script from Google API Console, under Credentials. Also see Credentials, access, security, and identity and Setting up OAuth 2.0

The client-secrets.json looks like this:

{"web":{
"client_id": "57...1t.apps.googleusercontent.com",
"project_id": "project-id-95...70",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "K2...y1",
"redirect_uris": ["https://script.google.com/oauthcallback"]
}}

The file would be deployed with the app code (App Engine). But an alternate location such as Cloud Storage would be possible.

OAuth2 access token

In absence of the user, I would like to use the client ID and secret with the same scopes that were granted to the script originally, for getting an access token from the Google authorization server, something like this:

HTTP 200 OK
Content-type: application/json
{
"access_token": "987tghjkiu6trfghjuytrghj",
"scope": "foo bar",
"token_type": "Bearer"
}

I would like to use the access token in the HTTP Bearer header for the requests to the Sheets API on behalf of the old script.

Client_credentials request to authorization server

My (limited) understanding is, that I can use the grant-type client_credentials to get the access token from the authorization server. The request would look like this:

POST /o/oauth2/token
Host: https://accounts.google.com
Content-Type: application/x-www-form-urlencoded
Accept: application/json
Authorization: Basic Base_64_string

grant_type=client_credentials&
scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fspreadsheets

Where the Basic HTTP authorization is client_id:client_secret values, separated by a colon, and base64 encoded.

If I ditch grant_type or scope in the body, I will get corresponding errors.

The version as above resulted in: {\n "error" : "invalid_request"\n} response, no specifics provided. I have tried with client_id and client_secret in the body in combination with and without the Authorization header, all with the same error.

Ani
  • 1,377
  • 1
  • 18
  • 29
  • are you able to store the refresh tokens for the users approving the original script? are you only going to be using sheets in the new script? same scopes? – Linda Lawton - DaImTo Apr 08 '18 at 19:21
  • No, that approvals happened under the hood and probably stores somewhere in Google Apps Script's environment. The new app could store it though. For now only Sheets API. And same scopes. The script will not be replaced entirely, only the web app part is migrated to App Engine. – Ani Apr 08 '18 at 19:25
  • 1
    Then you just answered your own question without a refresh token you can't access a users data. you will need to request access of the user again to access their data – Linda Lawton - DaImTo Apr 08 '18 at 19:55
  • I was afraid we would need to do so. Which means we have to ask all users twice, for the add-on and the web app. – Ani Apr 08 '18 at 19:57
  • 1
    if you want I can write you a wall of text answer in the morning. explaining why – Linda Lawton - DaImTo Apr 08 '18 at 20:18
  • @DaImTo This would be awesome! – Ani Apr 08 '18 at 20:39

1 Answers1

1

First Off let me start by saying that i am not an expert in app script or sheets i have used both a few times but i dont consider myself an expert in the subject.

When you authenticate someone their authentication is granted based upon the client id from within a project. They are granting you access to their data and approving the credential request. Think of it as a recored in Googles database someplace.

User 321 has granted consent to client 123 to access their sheets data.

So you have a project Super Script App which has client id 123 you are asking for access to edit sheets. If i approve it i am giving Super Script App with client id 123 access to my sheets. While i am sitting at my machine your app will run accessing my data. Now when i log off Super Script App with client id 123 cant access my data unless you have offline access and have a refresh token. With that refresh token you will be able to access my data when i am not there by requesting a new access token.

Now you want to make a new app. If you take the client id 123 and use it in your new app I am going to have to login to my google account but it will not popup and request that i give you permissions to access my data I have already granted client id 123 access to my sheets. Unless you have a refresh token your not going to be able to access this data without me being there.

If at anytime you ask for an extra scope I am going to have to approve that.

Now comes the fun part. I haven't actually tried this in a while. If you go to Google Developer console and create client id 321 under the same project as client id 123, and then use that in your new Super Script App V2 will i still have to grant it permission to access my data? I am going to lean towards probably not. Note a refresh token created with client id 123 will not work with client id 321 they are locked to a client unless its mobile and there is some magic their.

I am not exactly sure what you are doing with your second app so i hope this helps clear things up.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • Thank you. I probably should have clarified more in the question: The script is an add-on, where users have granted the scopes when they installed it. The script also has some "web app" features that work offline, i.e. while the resource owner is absent. For quota and other reasons this part is broken out into a new App Engine app. The add-on must still be installed by new users, but they normally never face the web app. The idea is the new web app uses the script's client secrets to obtain an access-token (in absence of resource owners) and use it for Sheets API access on behalf of them. – Ani Apr 09 '18 at 07:35
  • If you get that to work love to hear it. Security wise i am not sure i like the idea though. – Linda Lawton - DaImTo Apr 09 '18 at 07:48
  • I have tried the grant-type ["client_credentials"](https://tools.ietf.org/html/rfc6749#section-4.4) with Google authorization servers, using the default client ID of the add-on and also with an alternative client ID of it, but the best I get is `403 Forbidden` error with the description "invalid request". This gave me something more to google and I've found an [old answer](https://stackoverflow.com/questions/40102110/can-i-use-grant-type-client-credentials-for-google-api/40102951#40102951) of yours where you stated that Google auth servers don't support "client_credentials". – Ani Apr 17 '18 at 20:02
  • And as an additional note: I also have tried with service-accounts for the project of the add-on, but it will also not work without an additional user consent. Unless of course the service-account would be added to a G Suite domain's Admin Console > Security > Manage API client access, in which case no additional authorization is needed (as it is done by the admin). If the service-account has DWD it can also impersonate users in the domain. However, it seems that is isn't possible to share the authorization between the add-on and its web-app when implemented in GAE. – Ani Apr 17 '18 at 20:17
  • This is technically possible by using the getOAuthToken method of ScriptApp. If Google allows it, they are allowing the usage of the oAuthToken for purposes outside. It expires every hour, so make sure to have a strategy to update it. – Abhinav Das Mar 24 '23 at 15:30