Google OAuth 2 Refresh Token is Missing for Web Ap

2020-03-01 19:53发布

问题:

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.