Google OAuth 2 Refresh Token is Missing for Web App but Present for localhost
Asked Answered
C

0

43

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.

Carpet answered 16/11, 2018 at 17:41 Comment(20)
Do you use different users for localhost and cloud version testing ?Velum
@KavinduDodanduwa No. I have been testing with the same set of credentials and the same roles - varying them in a controlled manner to figure this out.Carpet
Can you change the user for cloud deployment and test ? I mean making a fresh user and try to obtain tokens ? Google have a weird policy on refresh tokens. If proper parameters are not given, G only issue refresh token at first time.Velum
@KavinduDodanduwa I am testing with multiple identities. See my next comment.Carpet
@KavinduDodanduwa - I saw a bug report on github. If I change from "prompt=select_account" to "prompt=consent" I now get a Refresh Token. I have not yet tested this extensively. Here is the link: github.com/googleapis/oauth2client/issues/453Carpet
If anyone knows the details behind the different between "prompt=select_account" and "prompt=consent" and any other options please post an answer with reference links. I will award the bounty to the best answer that defines this issue.Carpet
I think this is the answer for this - https://mcmap.net/q/215989/-get-refresh-token-google-apiVelum
As above answer highlights, we need to force consent obtaining to obtaining thew refresh token. But I bet you will get this first time .!Velum
@KavinduDodanduwa The bug report link differs with your link (which predates v2 I think). I think that the change to v2 created changes in the options. I am still trying to figure this out and document what exactly is wrong and what is the correct implementation.Carpet
@KavinduDodanduwa I tried using a brand new account. Actually during this debug period I also setup Google Identity and tested with new users created with that service. Same results.Carpet
@KavinduDodanduwa - one additional item. My GCP account is a G Suite organization. I am not sure if that makes a difference.Carpet
If that's the case this could very well be a bug. I guess they have conflict between prompt="select_account" and "consent". But as I said before, developers.google.com/identity/protocols/… highlights first time issuance of refresh token. Just search the word "first" and you will see it :)Velum
@KavinduDodanduwa - With "consent" I now receive a Refresh Token every time - both for the localhost version and the public version. With "select_account" I only receive a Refresh Token for localhost and never with the public version (new account,first time login, etc.). Tomorrow I will test refresh, revoke, etc. and try to better understanding what this change really means.Carpet
@KavinduDodanduwa - There is a weird behaviour related to your comment about the first issuance of a Refresh Token. It is Sunday night for me, I will investigate this more tomorrow. I updated my question with new information that I know as of now. I am not yet convinced that I have solved the problem.Carpet
Yes, please do have a fresh look on this. I have confidence about the first time issuance of refresh token. But still I am not sure about the behavior when prompt is set to "select_account". Yet again nothing can explain about localhost vs cloud deployment difference you observeVelum
Probably related: #11475601Grier
Going back to the basics, what kind of Google APIs are you using from your Flask app running GCE? The reason I ask is because only specific Google APIs require user permissions. Also curious why you're running your app on GCE instead of GAE or GCR,Dragrope
@Dragrope - the solution is in the last part of my question. In regards to your comment, none of your questions apply to OAuth Tokens. I am not aware of any Google APIs that do not require permission (user and/or service account). The key was requesting offline access when authenticating a user account.Carpet
You ONLY use refresh tokens with OAuth client IDs (user acct auth), meaning APIs accessing data owned by users (which is why I asked which APIs you're using), i.e., typically G Suite or YT APIs. Service acct auth is most prevalent w/GCP APIs as that data is typically owned by apps/projects rather than a human—such access doesn't require refresh tokens. There's also a 3rd type of credential, the API key. This is used by all Maps APIs, simple YT search, and many GCP ML APIs as they access public data, data not owned by users nor apps. Since it's not OAuth, no access nor refresh tokens for these.Dragrope
@Dragrope - I am not sure how your comments apply to offline access`. I understand OAuth in GCP very well. I am a Google GDE with inside access. I suggest you create a question and self answer. This thread is almost two years old. In regards to API Keys, they are now considered legacy and Google is phasing them out. Your reason for service accounts versus user accounts is not correct. The ownership of the data is not relevant to which method should be used.Carpet

© 2022 - 2024 — McMap. All rights reserved.