Linda Lawton - DaImTo
Linda Lawton - DaImTo

Reputation: 116938

How to authorize a curl script to Google Oauth after OAuth out-of-band (oob) flow is deprecated?

I have a curl script GoogleAuthenticationCurl.sh which i have been using for around ten years to request information from Googles different Google APIs.

This script users installed application credentials to build the consent screen for Googles oauth server. I copy the link and it shows the consent screen.

# Authorization link.  Place this in a browser and copy the code that is returned after you accept the scopes.
https://accounts.google.com/o/oauth2/auth?client_id=[Application Client Id]&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=[Scopes]&response_type=code

Google recently made a change which deprecated the redirect uri of urn:ietf:wg:oauth:2.0:oob. (#instructions-oob)

If I use the link i used to use i get the following

enter image description here

Google wants us to use redirect_uri=http://127.0.0.1:port or http://[::1]:port">http://[::1]:port instead of urn:ietf:wg:oauth:2.0:oob.

So I changed my link to the following and placed it in a web browser

https://accounts.google.com/o/oauth2/auth?client_id=[ClientId]&redirect_uri=http://127.0.0.1b&scope=profile&response_type=code

All went well in the beginning I was able to see the consent screen again and consent to authorization. But instead of getting a authorization code returned I got

enter image description here

This being due to the fact that I am not running a webpage I am just trying to authorize a curl script.

Is there anyway to get my curl script to respond to this request or have google completely removed the ability to authorize a curl Script now?

Upvotes: 5

Views: 4316

Answers (2)

Aki
Aki

Reputation: 21

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.

Upvotes: 2

Ron van der Heijden
Ron van der Heijden

Reputation: 15070

Deprecation

Google has deprecated the OOB flow, and so the redirect URL urn:ietf:wg:oauth:2.0:oob is removed since Feb 28, 2022. It is an unsafe feature for clients that cannot listen on an HTTP port.

Migration

You need to migrate to another flow. This does not necessarily mean you cannot use curl. But somehow, you need to be able to receive the redirect call with the necessary code.

Possible fix

  • Use the redirect URL http://127.0.0.1, notice the removed b at the end.
  • After the consent screen, check the URL, you probably find the code there http://127.0.0.1/?code=COPY_THIS_CODE.
  • Run the curl call to request the authorization codes.

Postscript

Postman could be interesting.

Upvotes: 6

Related Questions