How to Authorize Google APIs without repeated User Consent

Published:
Last Updated:
2 min read

There are two ways we can authenticate request to Google APIs. One way is API keys, the other way being OAuth. Whenever user data is involved, we have to go for OAuth, there's no other way. For the very first time, you'll authenticate request using Google's consent screen (Refer the image below).

google api consent screen

Problem

If the type of authentication is not service account, to authorize subsequent access to user data, you need to do some extra work. You can't really ask permission from user every time. We'll take an installed application as an example. For instance, let's say you run a job in the background daily to push data to the user's Google drive.

So, we've to preserve the authenticated state of the application somewhere to authorize future requests. In other words, when the user is not there to give permission. The approach is same for both web server and installed applications. The key here is refresh token.

Access and Refresh Tokens

Whenever a user authenticates an application upon pressing Allow on consent screen, an access token is provided to the application to access user's data on behalf of the user. That access token comes with an expiry time, so we can only use that for a certain amount of time.

Refresh token is something that doesn't expire. You can use it along with client ID and client secret to get umpteen number of access tokens, until the user revokes access to your application or the refresh token is blacklisted.

For web server application, to obtain refresh token, you've to pass access_type='offline' as a parameter while obtaining authorization URL. In the case of installed application, access type is by default offline. Here offline means, getting access tokens using refresh token when the user is not there to provide permission.

Let's understand the concept with an installed application example. Check the below code. A credentials object is built with client id & client secret which is eventually passed to build a drive object before firing API request.

Note: For demonstration purpose, I'm using Python. Same result can be achieved using any language and appropriate libraries supported by Google.

from google_auth_oauthlib.flow import InstalledAppFlow
from apiclient.discovery import build

SCOPES = ['https://www.googleapis.com/auth/drive.file']
client_config = {
    "installed": {
        "auth_uri": "https://accounts.google.com/o/oauth2/auth",
        "token_uri": "https://accounts.google.com/o/oauth2/token",
        "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob"],
        "client_id": "MY CLIENT ID",
        "client_secret": "MY CLIENT SECRET"
    }
}

flow = InstalledAppFlow.from_client_config(client_config, SCOPES)
credentials = flow.run_console()

drive = build('drive', 'v3', credentials=credentials)
resp = drive.files().list().execute()
print(resp)

When the script calls run_console(), user'll be asked to visit a URL to get an authorization code. Authentication & Authorization both complete here. After providing it, the list of all files managed by the application is printed.

We were able to print the files, as we got authorization code from the user. What if we want to run the script daily to check the list of files managed by the application? Here we need the help of refresh token. You can get it easily from the credentials object defined above. All you have to do is save it somewhere for later use.

access_token = credentials.token
refresh_token = credentials.refresh_token

Now, let's assume you've refresh token, client ID and client secret. You can use refresh token to authorize API access to user data without getting permission again from the user. Refer below code.

from google.oauth2.credentials import Credentials
from apiclient.discovery import build

credentials = Credentials(
    None,
    refresh_token="SAVED REFRESH TOKEN",
    token_uri="https://accounts.google.com/o/oauth2/token",
    client_id="MY CLIENT ID",
    client_secret="MY CLIENT SECRET"
)

drive = build('drive', 'v3', credentials=credentials)
resp = drive.files().list().execute()
print(resp)

In the above script, new access token is generated every time. To avoid this, None can be replaced by the access token mentioned before. If the access token is not yet expired & provided to Credentials class, it'll be used, else refresh token kicks in and a new access token is generated. Hence, this script runs without requiring user consent again and again.