Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why do i not get a new refresh token returned with every authorization request? #1219

Closed
LindaLawton opened this issue Jan 25, 2023 · 4 comments
Labels
triage me I really want to be triaged.

Comments

@LindaLawton
Copy link

The following example lists a users messages from the Gmail api using web flow. It works fine, feel free to test with your own web credentials from google cloud console.

My question is. Why does the authorization only return a refresh token after the first authorization request. I have added offline access, the first time the user authorizes this code a refresh token is returned. It is not returned again, the only way to force it to return again is to add prompt= "consent". (this is commented out in the code for testing)

I have had to add a check in the oauth2callback method in order to check that there is a refresh token and to not save my credentials if the refresh token does not exist, as it was over writing it every time the access token was refreshed.

if not os.path.exists('token.json') or credentials.refresh_token:

Is there something that I need to set on the client to ensure that it always returns a new refresh token when the access token is refreshed. I thought this was a standard feature in Google authorization server it i always get a new refresh token in other languages. Or is the issue that this is a web client?

code

import os

import flask
from flask import Flask,redirect,render_template,url_for, request
app = Flask(__name__, template_folder='templates')
import google.auth.exceptions
import google_auth_oauthlib.flow
import ssl
context = ssl.SSLContext()
context.load_cert_chain('C:\Development\FreeLance\GoogleSamples\Python\cert.pem', 'C:\Development\FreeLance\GoogleSamples\Python\key.pem')
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from googleapiclient.errors import HttpError
from googleapiclient.discovery import build
import google_auth_oauthlib.flow

SCOPES = ['https://mail.google.com/']

REDIRECT_URI = 'https://127.0.0.1:5000/oauth2callback'

CREDENTIALS = 'C:\Development\FreeLance\GoogleSamples\Credentials\CredWebEverything.json'

def get_flow():

    # Initialize the flow using the client ID and secret downloaded earlier.
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        CREDENTIALS,
        scopes= SCOPES,
    )
    # Indicate where the API server will redirect the user after the user completes
    # the authorization flow. The redirect URI is required.
    flow.redirect_uri = REDIRECT_URI

    return flow


def redirect_user():

    flow = get_flow()
    # Generate URL for request to Google's OAuth 2.0 server.
    # Use kwargs to set optional request parameters.
    authorization_url, state = flow.authorization_url(
        # Enable offline access so that you can refresh an access token without
        # re-prompting the user for permission. Recommended for web server apps.
        access_type='offline',
        # Enable incremental authorization. Recommended as a best practice.
        include_granted_scopes='false',
        # Forces a new refresh token when we authorize the application a second time.
        #prompt= "consent"
        )

    return authorization_url, state

@app.route('/login')
def login():
    authorization_url, state = redirect_user()
    return flask.redirect(authorization_url)

@app.route('/listmessages')
def gmail_list_messages():
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        try:
            creds = Credentials.from_authorized_user_file('token.json', SCOPES)
            print(f'Credentials exist refreshing.')
            creds.refresh(Request())
        except google.auth.exceptions.RefreshError as error:
            # if refresh token fails, reset creds to none.
            creds = None
            print(f'An error occurred: {error}')
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            # If it's not logged in then it's going to force it to.
            authorization_url, state = redirect_user()
            print(f'Credentials do not exist requesting authorization.')
            return flask.redirect(authorization_url)
    try:
        service = build('gmail', 'v1', credentials=creds)

        # Call the Gmail v1 API
        results = service.users().messages().list(
            userId='me').execute()
        messages = results.get('messages', [])
    except HttpError as error:
        # TODO(developer) - Handle errors from gmail API.
        print(f'An error occurred: {error}')

    return render_template("mail.html", data=messages)



@app.route('/')
def index():
    return render_template('index.html', title="Home Page")

@app.route('/oauth2callback')
def oauth2callback():
    flow = get_flow()

    auth_code = request.args['code']
    flow.fetch_token(code=auth_code)
    credentials = flow.credentials

    # saving the credentials for later. Note: A refresh does not return a new refresh token.
    if not os.path.exists('token.json') or credentials.refresh_token:
        print(f'Storing credentials: {credentials.to_json()}')
        with open('token.json', 'w') as token:
            token.write(credentials.to_json())

    return redirect("/listmessages")

if __name__ == '__main__':
    # Bind to PORT if defined, otherwise default to 5000.
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port, ssl_context=context)
@parthea parthea transferred this issue from googleapis/google-api-python-client Jan 26, 2023
@parthea
Copy link
Contributor

parthea commented Jan 26, 2023

Hi @LindaLawton,

I'm transferring this issue to the team working on the python auth library to ensure that you receive quality answers.

@clundin25 clundin25 added the triage me I really want to be triaged. label Jan 27, 2023
@clundin25
Copy link
Contributor

Hi @LindaLawton. Can you provide an expand on where you are seeing a new refresh token in other languages?

Please see the documentation regarding refreshing a access token https://developers.google.com/identity/protocols/oauth2/native-app#offline.

@clundin25
Copy link
Contributor

New refresh tokens are optional in the access token refresh response. https://www.rfc-editor.org/rfc/rfc6749#section-6

Please re-open this if you have any further questions! Thanks!

@LindaLawton
Copy link
Author

@clundin25 thanks I will pass that on to my client that's exactly what i was looking for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
triage me I really want to be triaged.
Projects
None yet
Development

No branches or pull requests

3 participants