I followed this Gmail API Python Quickstart tutorial:
https://developers.google.com/gmail/api/quickstart/python
I configured my OAuth client ID in the API console as instructed (see the first image below). However, launching the script opens a browser session that results in the 400 error below.
The redirect URL matches what is registered in the API console.
HOWEVER, the quickstart script opens the following URL:
https://accounts.google.com/o/oauth2... &redirect_uri=http%3A%2F%2Flocalhost%3A8080%2F ...
Manually changing redirect URI to http://localhost:8080 partially fixes the problem as I proceed to the authorization request, but then the response can't get returned to the command prompt.
How can I force the quickstart program to produce a URL that leaves the redirect URI as http://localhost:8080?
The cause of the error you're getting is that the Python Quickstart says:
d. Select the Credentials tab, click the Create credentials button and select OAuth client ID.
e. Select the application type Other, enter the name "Gmail API Quickstart", and click the Create button.
However, looking at what you're doing, you are using Web Application instead of Other.
When I used the Other as client_secret.json, I didn't encounter this problem.
result:
The authentication flow has completed.
No hacks/workaround needed. Just follow instructions :)
Explanation
This is happening because the uri_redirect parameter being passed to the Google API server is a percent encoded ASCII string. This can be verified by looking at the URL launched by the script:
...redirect_uri=http%3A%2F%2Flocalhost%3A8080%2F...
The whole URL gets encoded by the quickstart script in the steps following the execution of this line:
credentials = tools.run_flow(flow, store, flags)
Stepping through the process with a debugger reveals the URL is eventually encoded with the urlencode method from Python's urllib library. This results in the redirect_uri parameter from the client_secret.json file converting from:
http://localhost:8080
To
http%3A%2F%2Flocalhost%3A8080%2F
When Google receives the OAuth request, it compares the encoded uri_redirect parameter against the un-encoded one registered in the API console. Since they don't match, a redirect_uri_mismatch is returned.
Solution
Ideally, Google should modify the API endpoint to ensure that this parameter is decoded if necessary before comparing it to what's registered in the API console.
An acceptable fix would be if the API console would accept encoded redirect URI entries, but it does not:
Workaround (warning: hacky)
Simply replace the encoded redirect_uri parameter in two places in the oauth2client library:
(1) update_query_params function in _helpers.py
...
start = new_query.find("redirect_uri")+13
end = new_query.find("&",start)
new_query2 = new_query[:start] + "http://localhost:8080" + new_query[end:]
new_parts = parts._replace(query=new_query2)
...
(2) step2_exchange in client.py
...
body = urllib.parse.urlencode(post_data)
start = body.find("redirect_uri")+13
end = body.find("&",start)
body2 = body[:start] + "http://localhost:8080" + body[end:]
headers = {
'content-type': 'application/x-www-form-urlencoded',
}
if self.authorization_header is not None:
headers['Authorization'] = self.authorization_header
if self.user_agent is not None:
headers['user-agent'] = self.user_agent
if http is None:
http = transport.get_http_object()
resp, content = transport.request(
http, self.token_uri, method='POST', body=body2, headers=headers)
...
Now everything works.