7

I am following this example in an attempt to authenticate my users with the Google API.
Here is the relevant code I am using, almost exactly as in the example:

@app.route('/login/')
def login():
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        'client_secret.json',
        scopes=['https://www.googleapis.com/auth/drive.file'])
    flow.redirect_uri = 'https://localhost:5000/oauth2callback/'
    authorization_url, state = flow.authorization_url(
        access_type='offline',
    include_granted_scopes='true')
    session['state'] = state
    return redirect(authorization_url)

#called by Google's oauth 
@app.route('/oauth2callback/')
def oauth2callback():
    state = request.args['state']
    # Use the client_secret.json file to identify the application requesting
    # authorization. The client ID (from that file) and access scopes are required.
    state = session['state']
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        'client_secret.json',
        scopes=['https://www.googleapis.com/auth/drive.file'],
        state=state)
    flow.redirect_uri = url_for('oauth2callback', _external=True)

    authorization_response = request.url
    flow.fetch_token(authorization_response=authorization_response, code_verifier=False)

When I do this, it gives the error, oauthlib.oauth2.rfc6749.errors.InvalidGrantError: (invalid_grant) Missing code verifier.

I do not know what a code verifier is, nor did the examples mention one.

The stack trace is available if anyone thinks it will help

How can I solve this issue? Any help is appreciated!

3
  • 3
    I get the same error when using google-auth-oauthlib==0.4.0. However, the error does not appear in 0.3.0. So I guess that something has changed in 0.4.0. Commented Jun 12, 2019 at 7:14
  • 1
    A related upstream issue: github.com/googleapis/google-auth-library-python-oauthlib/… Commented Jun 12, 2019 at 9:37
  • 1
    Well, stackoverflow to the rescue again. pip uninstall google-oauth-lib && pip install google-oauth-lib==0.3.0 has solved this issue. Should I post this as an answer? It feels... hacky. Is google's package just broken? How can I add a valid code_verifier? Commented Jun 12, 2019 at 14:55

2 Answers 2

8

This seems to be a bug in the 0.4.0 version of google-auth-oauthlib (see this upstream issue; note that it has been reported after this SO question was posted).

You have the following options:

  1. As a workaround, you can downgrade the used version:
    pip install --upgrade google-auth-oauthlib==0.3.0
    
  2. Pass a custom code verifier when instantiating google_auth_oauthlib.flow.Flow(), which should be a random string of 43-128 characters used to verify the key exchange using PKCE:
    oauth2_session, client_config = google_auth_oauthlib.helpers.session_from_client_secrets_file(
        'client_secret.json',
        scopes=['https://www.googleapis.com/auth/drive.file']),
    )
    flow = google_auth_oauthlib.flow.Flow(
        oauth2_session,
        client_type='web',
        client_config=client_config,
        redirect_uri='https://localhost:5000/oauth2callback/',
        code_verifier='<random string>'
    )
    
    Note: The code is for your login() function. You will have to slightly adjust it to work in your oauth2callback() function.
  3. Wait until the bug is fixed in upstream (provided that it is a bug). After that, the code verifier will be auto-generated when not provided.
Sign up to request clarification or add additional context in comments.

3 Comments

The verifier can also be set after construction (either constructor or class method): flow.code_verifier = '<random string>'
The answer from @Jerther seems more valid, as it is a random string. Creating your own is more overhead or a shortcut using hash or a constant which lowers security.
Agree with @PaulKenjora ... plus the bug has been fixed. I'm now using 1.1.0 which works if you provide a valid code_verifier, a bonus if the library can take the responsibility with a single flag.
5

Based on s3rvac's answer, I dug a bit into the source code and found that the code_verifier property can in fact be auto generated in the authorization_url() method! So no need to generate one yourself.

First when you first construct the Flow object, you must enable autogenerate_code_verifier:

flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        'client_secret.json',
        scopes=['https://www.googleapis.com/auth/drive.file'],
        autogenerate_code_verifier=True  # <-- ADD THIS HERE
)

Then in your login() method, after the call to flow.authorization_url(), store flow.code_verifier in the session:

authorization_url, state = flow.authorization_url(
        access_type='offline', 
        include_granted_scopes='true')  # code_verifier is set in this method
session['code_verifier'] = flow.code_verifier  # Get and store the code
session['state'] = state
return redirect(authorization_url)

Then, in the callback method, just load it back:

flow = google_auth_oauthlib.flow.Flow(
    oauth2_session,
    client_type='web',
    client_config=client_config,
    redirect_uri='https://localhost:5000/oauth2callback/',
    code_verifier=session.get('code_verifier')  # Load the code. Don't set autogenerate_code_verifier here, otherwise code_verifier would be overwritten.
)

I use Flow.from_client_config() to construct a Flow but I just add this line and it works fine:

flow.code_verifier = session.get('code_verifier')

2 Comments

Worked as described. Probably more secure this way as each auth flow has a unique session.
Apparently, the next code change to flow.py that happened like a month after I originally posted this answer was to remove the automatic generation of code_verifier by default, and add a flag to enable it. I updated my answer to reflect that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.