Problem: Missing OAuth 2 Refresh Token.
The problem is that the localhost version receives a Refresh Token
as part of the granted token but the same code running in GCE does not.
Details:
I have written a Python Flask application that implements Google OAuth 2.0. This web application runs in the cloud with a verified domain name, valid SSL certificate and HTTPS endpoint. This web application unmodified also runs as localhost
. The differences between the runtime is that the localhost version does not use TLS. There are no other differences in the code flow.
Other than the Refresh Token
is missing and I cannot automatically renew a token
, everything works perfectly.
I have researched this issue extensively. API problems such as access_type=offline
etc are correctly implemented otherwise I would not get a Refresh Token
in the localhost
version.
I am using the requests_oauthlib
python library.
gcp = OAuth2Session(
app.config['gcp_client_id'],
scope=scope,
redirect_uri=redirect_uri)
# print('Requesting authorization url:', authorization_base_url)
authorization_url, state = gcp.authorization_url(
authorization_base_url,
access_type="offline",
prompt="select_account",
include_granted_scopes='true')
session['oauth_state'] = state
return redirect(authorization_url)
# Next section of code after the browser approves the request
token = gcp.fetch_token(
token_url,
client_secret=app.config['gcp_client_secret'],
authorization_response=request.url)
The token has refresh_token
when running in localhost
but not when running with in the cloud.
This Google document discusses refresh tokens, which indicates that this is supported for web applications.
Refreshing an access token (offline access)
[Update 11/18/2018]
I found this bug report which gave me a hint to change my code from this:
authorization_url, state = gcp.authorization_url(
authorization_base_url,
access_type="offline",
prompt="select_account",
include_granted_scopes='true')
to this:
authorization_url, state = gcp.authorization_url(
authorization_base_url,
access_type="offline",
prompt="consent",
include_granted_scopes='true')
Now I am receiving the Refresh Token in the public server version and the localhost version.
Next I searched for documentation on the prompt
option and found this:
OpenID Conect prompt
prompt (Optional)
A space-delimited list of string values that specifies whether the authorization server prompts the user for reauthentication and consent. The possible values are:
none The authorization server does not display any authentication or user consent screens; it will return an error if the user is not already authenticated and has not pre-configured consent for the requested scopes. You can use none to check for existing authentication and/or consent.
consent The authorization server prompts the user for consent before returning information to the client.
select_account The authorization server prompts the user to select a user account. This allows a user who has multiple accounts at the authorization server to select amongst the multiple accounts that they may have current sessions for.
If no value is specified and the user has not previously authorized access, then the user is shown a consent screen.
I think the Google documentation should be updated. On the same page, the following text appears:
access_type (Optional)
The allowed values are offline and online. The effect is documented in Offline Access; if an access token is being requested, the client does not receive a refresh token unless offline is specified.
That statement caused me a lot of confusion trying to debug why I could not obtain a Refresh Token for the public server version but I could for the localhost version.