If you have ever wanted to test Google OAuth 2.0 flows from the command line, you will like this short article.
This article is the second version. I wrote a previous article on using curl, but that version did not have a custom web server to handle the OAuth callback. This version includes a web server to automate the entire process. Read the first version for an introduction on using curl with OAuth.
Also, see my next article on how to refresh an access token:
Google OAuth 2.0 – Testing with Curl – Refresh Access Token
This article is for Windows Command Prompt users but should be easily adaptable to Linux and Mac.
Download Git Repository
I have published the files for this article on GitHub.
https://github.com/jhanley-com/google-oauth-2-0-testing-with-curl
License: MIT License
Clone my repository to your system. The code in this article is in directory v2.
1 |
git clone https://github.com/jhanley-com/google-oauth-2-0-testing-with-curl.git |
Details:
You will need your Google Client ID
and Client Secret
. These can be obtained from the Google Console under APIs & Services
-> Credentials
. In the following example code, these are stored in the file /config/client_secrets.json
These examples also use the program jq
for processing the Json output. You can download a copy here.
In the following example, the Scope is cloud-platform
. Modify to use the scopes that you want to test with. Here are a few scopes that you can test with:
1 2 3 4 5 6 7 |
"https://www.googleapis.com/auth/cloud-platform" "https://www.googleapis.com/auth/cloud-platform.read-only" "https://www.googleapis.com/auth/devstorage.full_control" "https://www.googleapis.com/auth/devstorage.read_write" "https://www.googleapis.com/auth/devstorage.read_only" "https://www.googleapis.com/auth/bigquery" "https://www.googleapis.com/auth/datastore" |
OAuth 2.0 Scopes for Google APIs
Note that in this example the code uses three scopes. The second two are for the Client ID Token.
1 2 3 |
https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile |
Details:
- Copy the following statements to a Windows batch file.
- Modify to fit your environment.
- Modify the script for the browser that you want to use.
- Run the batch file.
- A browser will be launched.
- The browser will go to https://accounts.google.com where you can complete the Google OAuth 2.0 authentication.
- The script will complete the OAuth 2.0 code exchange for a Token.
- The Token will be displayed in the command prompt.
The returned Token contains the Access Token, Refresh Token, and Client ID Token.
Windows Batch Script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
@set FILE=\config\client_secrets.json @set CMD_1=jq -r ".installed.client_id" %FILE% @set CMD_2=jq -r ".installed.client_secret" %FILE% @for /f %%i in ('%CMD_1%') do set CLIENT_ID=%%i @for /f %%i in ('%CMD_2%') do set CLIENT_SECRET=%%i @echo Client ID: %CLIENT_ID% @echo Client Secret: %CLIENT_SECRET% set SCOPE=https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile set ENDPOINT=https://accounts.google.com/o/oauth2/v2/auth set URL="%ENDPOINT%?client_id=%CLIENT_ID%&response_type=code&scope=%SCOPE%&access_type=offline&redirect_uri=http://localhost:9000" @REM start iexplore %URL% start chrome %URL% @REM start microsoft-edge:%URL% @REM Run the webserver and store the code in a file python webserver.py > code.txt set /p AUTH_CODE=<code.txt curl ^ --data client_id=%CLIENT_ID% ^ --data client_secret=%CLIENT_SECRET% ^ --data code=%AUTH_CODE% ^ --data redirect_uri=http://localhost:9000 ^ --data grant_type=authorization_code ^ https://www.googleapis.com/oauth2/v4/token > oauth.token jq -r ".access_token" oauth.token > access.token set /p ACCESS_TOKEN=<access.token jq -r ".refresh_token" oauth.token > refresh.token set /p REFRESH_TOKEN=<refresh.token jq -r ".id_token" oauth.token > id.token set /p ID_TOKEN=<id.token echo "Token Information:" curl -H "Authorization: Bearer %ACCESS_TOKEN%" https://www.googleapis.com/oauth2/v3/tokeninfo echo "User Information:" curl -H "Authorization: Bearer %ACCESS_TOKEN%" https://www.googleapis.com/oauth2/v3/userinfo |
Web Server Python code (webserver.py):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
############################################################ # Version 0.90 # Date Created: 2018-11-17 # Last Update: 2018-11-17 # https://www.jhanley.com # Copyright (c) 2018, John J. Hanley # Author: John Hanley ############################################################ """ This program implements a webserver for receiving the OAuth 2.0 code This is a single shot web server. Only one request is processed and then the web server exits. """ import sys from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import urlparse, parse_qsl # The listening port can be anything but must be available web_port: int = 9000 class ProcessRequests(BaseHTTPRequestHandler): """ Web server callback function to process GET, POST, etc. """ def log_message(self, format, *args): """ This function noops the log message for requests received. """ return def do_GET(self): """ Process the web server GET request. """ rcvd_code = None # # process the query parameters. We are looking for a "code". # # sys.stderr.write('Query: {}\n'.format(urlparse(self.path).query)) items = parse_qsl(urlparse(self.path).query) for item in items: if 'code' in item: # system.stderr.write('Found item: {} = {}\n'.format(item[0], item[1])) sys.stdout.write(item[1]) rcvd_code = item[1] if 'error' in item: # system.stderr.write('Found item: {} = {}\n'.format(item[0], item[1])) sys.stderr.write(item[1] + '\n') if rcvd_code is None: sys.stderr.write('Error: Invalid request: {}\n'.format(self.path)) sys.stderr.write('Error: Request does not include query param "code=value"') self.send_response(500) self.send_header('Content-type', 'text/html') self.end_headers() return # Notice that this url is 'https' which must be specified for the redirect # FIX self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() html = b""" "<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head> <body>Please return to the app.</body></html>") """ self.wfile.write(html) def run_local_webserver(server_class=HTTPServer, handler_class=ProcessRequests, port=web_port): """ This function implements a web server. Once the web browser calls this server with a 'code', the server prints the code and exits. """ server_address = ('', port) try: httpd = server_class(server_address, handler_class) except: e_type = sys.exc_info()[0] e_msg = sys.exc_info()[1] sys.stderr.write('\n') sys.stderr.write('****************************************\n') sys.stderr.write('Error: Cannot start local web server on port {}\n'. format(web_port)) sys.stderr.write('Error: %s\n', e_type) sys.stderr.write('Error: %s\n', e_msg) exit(1) # sys.stderr.write('Starting httpd...\n') sys.stderr.write('Listening on port {} ...\n'.format(web_port)) httpd.handle_request() # All done. if __name__ == "__main__": run_local_webserver() exit(0) |
I design software for enterprise-class systems and data centers. My background is 30+ years in storage (SCSI, FC, iSCSI, disk arrays, imaging) virtualization. 20+ years in identity, security, and forensics.
For the past 14+ years, I have been working in the cloud (AWS, Azure, Google, Alibaba, IBM, Oracle) designing hybrid and multi-cloud software solutions. I am an MVP/GDE with several.
February 14, 2019 at 5:13 AM
Thanks for sharing. I could run *.cmd(or *.bat) from WSL, except python. I’ll post in my blog
Since jq.exe is small and curl.exe is provided by Microsoft. I just used them bot not Python.
September 19, 2021 at 10:51 AM
Thank you for sharing this invaluable post.
In fact I have adapted this and the first version of your post (from Stackoverflow) to work with Mendeley API.
September 19, 2021 at 2:26 PM
Thank you. It always makes me happy when my work helps others. Keep your feet in the cloud!