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.

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:

"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.

https://www.googleapis.com/auth/cloud-platform
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile

Details:
  1. Copy the following statements to a Windows batch file.
  2. Modify to fit your environment.
  3. Modify the script for the browser that you want to use.
  4. Run the batch file.
  5. A browser will be launched.
  6. The browser will go to https://accounts.google.com where you can complete the Google OAuth 2.0 authentication.
  7. The script will complete the OAuth 2.0 code exchange for a Token.
  8. 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:

@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):

############################################################
# Version 0.90
# Date Created: 2018-11-17
# Last Update:  2018-11-17
# https://www2.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)