Solution
So, this can be solved by using redirect uri loopback. More on why here
Explanation below the script.
#!/bin/sh
# Tutorial https://www.daimto.com/how-to-get-a-google-access-token-with-curl/
# YouTube video https://youtu.be/hBC_tVJIx5w
# Client id from Google Developer console
# Client Secret from Google Developer console
# Scope this is a space seprated list of the scopes of access you are requesting.
CLIENT_ID=
CLIENT_SECRET=
SCOPE="https://www.googleapis.com/auth/drive"
REDIRECT_URI="http%3A//localhost"
TOKEN_URL="https://accounts.google.com/o/oauth2/token"
AUTHORIZATION_CODE_REGEX='[0-9]/[0-9A-Za-z_-]+'
TMPFILE="$(mktemp)"
[ -z "${CLIENT_ID:+${CLIENT_SECRET}}" ] && printf "Client ID or SECRET not set.\n" && return 1
server_string='Now go back to command line..'
server_port='8079'
# run a loop until a closed port has been found
# check for 50 ports
while :; do
: "$((server_port += 1))"
if [ "${server_port}" -gt 8130 ]; then
"${QUIET:-_print_center}" "normal" "Error: No open ports found ( 8080 to 8130 )." "-"
return 1
fi
{ curl -Is "http://localhost:${server_port}" && continue; } || break
done
# start the server to be used as loopback ip address
# try to start the server using netcat or python3
# https://docs.python.org/3/library/http.server.html
if command -v python 1> /dev/null && python -V | grep -q 'Python 3'; then
python << EOF 1> "${TMPFILE}" 2>&1 &
from http.server import BaseHTTPRequestHandler, HTTPServer
class handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
if '/?code' in self.path:
message = '${server_string}'
self.wfile.write(bytes(message, "utf8"))
with HTTPServer(('', ${server_port}), handler) as server:
server.serve_forever()
EOF
server_pid="${!}"
elif command -v nc 1> /dev/null; then
# https://stackoverflow.com/a/58436505
printf "%b" "HTTP/1.1 200 OK\nContent-Length: $(printf "%s" "${server_string}" | wc -c)\n\n${server_string}" | nc -l -p "${server_port}" 1> "${TMPFILE}" 2>&1 &
server_pid="${!}"
else
printf "Error: neither netcat (nc) nor python3 is installed. It is required to required a http server which is used in fetching authorization code. Install and proceed.\n"
return 1
fi
# https://developers.google.com/identity/protocols/oauth2/native-app#obtainingaccesstokens
code_challenge="$(date '+%s')_authorization_code"
printf "Visit the below URL, follow the instructions and then come back to commandline\n"
URL="https://accounts.google.com/o/oauth2/auth?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}%3A${server_port}&scope=${SCOPE}&response_type=code&code_challenge_method=plain&code_challenge=${code_challenge}"
printf "\n%s\n\n" "${URL}"
printf "Press enter if you have completed the process in browser\n"
read -r _
# kill the server pid
kill "${server_pid}"
if ! authorization_code="$(grep -m1 'GET.*code.*HTTP/1.1' < "${TMPFILE}" | sed -e 's/.*GET.*code=//' -e 's/\&.*//')" &&
printf "%s\n" "${authorization_code}" | grep -q "${AUTHORIZATION_CODE_REGEX}"; then
printf "Code was not fetched properly , here is some info that maybe helpful.. "
printf "%s\n" "Code that was grabbed: ${authorization_code}"
printf "Output of http server:\n"
cat "${TMPFILE}"
(rm -f "${TMPFILE}" &)
return 1
fi
(rm -f "${TMPFILE}" &)
# Print the refresh token json to stdout
# https://developers.google.com/identity/protocols/oauth2/native-app#handlingresponse
curl --compressed -X POST \
--data "code=${authorization_code}&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&redirect_uri=${REDIRECT_URI}%3A${server_port}&grant_type=authorization_code&code_verifier=${code_challenge}" \
"${TOKEN_URL}"
Explanation
This method additionally needs code_challenge
and code_challenge_method
to be passed with the url parameters. Also redirect uri needs to be same as provided in credentials.json
( which is usually http://localhost
.
code_challenge
can be generated in two ways, i will use the easy way. Easy way basically means a random string.
code_challenge_method
can be plain
( easy way ) and S256
.
Then we need to start a local http server with a available port. To start the server in the script, i have used python or netcat, whatever available.
redirect_uri
will be http://localhost:8080
Then visit the url in browser and follow steps, allow and done.
Now to get refresh token
using this authorization code, code_verifier
parameter is added to the token fetch request. code_verifier
is same as code_challenge
.
P.S
I recently implemented this in my akianonymus/gdrive-downloader. See https://github.com/Akianonymus/gdrive-downloader/blob/master/src/common/auth-utils.sh#L339 for reference.