11

Assuming there are three steps,

  1. Getting a device code,
  2. Getting an authentication token,
  3. Connect to Google Drive.

STEP 1: I get through Step 1 if (and only if) I ignore the redirect_url parameter listed necessary on various how-to links. So it's...

curl -d 'client_id=*client_id*' -d 'scope=https://www.googleapis.com/auth/drive.file' -d 'response_type=code' 'https://accounts.google.com/o/oauth2/device/code'

At that point the return is... {"device_code": "[device_code]", "user_code": "[user_code]", "expires_in": 1800, "interval": 5, "verification_url": "https://www.google.com/device"}

So far so good.

STEP 2: This is where I get stuck. Tried various iterations of the following:

curl -H 'Content-Type: application/x-www-form-urlencoded' -d 'client_id=**client_id**' -d 'client_secret=*client_secret*' -d 'grant_type=authorization_code' -d 'redirect_uri=urn:ietf:wg:oauth:2.0:oob' -d 'code=[device_code_from_above]' 'https://accounts.google.com/o/oauth2/token'

The above returns

{"error" : "invalid_grant", "error_description" : "Malformed auth code."}

If grant_type is changed to 'http://oauth.net/grant_type/device/1.0', response is

{"error" : "invalid_request", "error_description" : "Parameter not allowed for this message type: redirect_uri"}

If redirect_uri is removed the response is

{"error" : "authorization_pending"}

The above cURL attempts were cobbled together referencing the following links... https://developers.google.com/identity/protocols/OAuth2ForDevices

http://www.visualab.org/index.php/using-google-rest-api-for-analytics#comment-157284

list google drive files with curl

Google Drive not listing files in folder

Not able to fetch Google OAuth 2.0 access token

https://www.daimto.com/google-authentication-with-curl/

Stymied!

EDIT

Per request, the goal here: develop a method of being alerted when files upload, and have a system in place that can selectively and systematically download based on a variety of queries.

The reason we're not doing this with Google Drive's web UI: The file sizes are pretty big: 10-50gb per file, and Google can't batch download without zipping first, and can't zip anything over a size that's smaller than our smallest file.

The reason we're not doing this with Google Drive's APP: It's not possible (AFAIK) to manage which files do and don't download locally, and there's no ability (again AFAIK) to store to an external volume.

Also, we're integrating a workflow database into our media uploads and downloads: tracking, dates, progress notes, versions, etc., none of which is part of any existing Google system. So the goal here is to see what's options Google's API might hold for all this.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
WhatsYourFunction
  • 621
  • 1
  • 9
  • 25
  • what exactly are you pasting into the "device_code_from_above". Feel free to obfuscate the actual code values for security. It should be something like "4/AADhY_JAsynoD1SEAMbmt-oVmO_kWbFJ_XV3qap7XVd7iAmpjrgkOa5kAefLEpl38pcKEBXIOxQ568q_kzU" – pinoyyid Jul 17 '18 at 06:36
  • Step 1 returns "device_code" as a 98-character string that earlier began with "4/AA", more recently "AH-1N". – WhatsYourFunction Jul 17 '18 at 13:20
  • "AH..." is not an Auth Code, hence the error. Are you sure you are cancelling the authorisation between test runs, and are you confusing device code with Auth code. – pinoyyid Jul 17 '18 at 15:10
  • It might be worth updating you question to explain your scenario and what you are looking to achieve. It isn't possible to authorise an API/app without a browser because before a user can give his authorisation, he must first be authenticated and that is done via a Google web login and session cookies. – pinoyyid Jul 17 '18 at 16:53

2 Answers2

8

step one get code

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

Put this into a browser window. And copy the code there into step two. I suspect the issue is with the code you are getting returned from the first step

step two exchange code

There was a link to the gist for the code used in my blog post you linked.

As you can see the post data should be sent as one long query string separated with &

--data 'client_id=[Application Client Id]&client_secret=[Application Client Secret]&refresh_token=[Refresh token granted by second step]&grant_type=refresh_token' \

Code ripped from googleauthenticationcurl.sh

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

# 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

# Exchange Authorization code for an access token and a refresh token.

curl \
--request POST \
--data "code=[Authentcation code from authorization link]&client_id=[Application Client Id]&client_secret=[Application Client Secret]&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code" \
https://accounts.google.com/o/oauth2/token

# Exchange a refresh token for a new access token.
curl \
--request POST \
--data 'client_id=[Application Client Id]&client_secret=[Application Client Secret]&refresh_token=[Refresh token granted by second step]&grant_type=refresh_token' \
https://accounts.google.com/o/oauth2/token

-d with out (')'s

This appears to work fine. I removed the header which isnt needed and all the (')'s you had in your code I didnt have any issues getting a refresh token returned

curl -d client_id=103456123799103-vpdthl4ms0soutcrpe036ckqn7rfpn.apps.googleusercontent.com -d client_secret=uxpj6hx1H2N5BFqdnaNhIbie -d grant_type=authorization_code -d redirect_uri=urn:ietf:wg:oauth:2.0:oob -d code=4/AABvK4EPc__nckJBK9UGFIhhls_69SBAyidj8J_o3Zz5-VJN6nz54ew https://accounts.google.com/o/oauth2/token

response:

{
  "access_token" : "pO4LBSreV_r2i8kPklXVTqylXbMXip4OmQ0ZgRW0qZ8_b1ZP_zPJv0Xc_Qqsj9nM5ryWb7C81dYNFkO_bC6ifWA68dIlz40a0owG4GWpbZ2ufkHNXgre4",
  "expires_in" : 3600,
  "refresh_token" : "1/mEADfx6ffWULNBNFrKnlqOlK1uGL8Z546qBCHg",
  "token_type" : "Bearer"
}
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • the OP *is* using POST. When `-d` is included on the curl command line, curl switches from GET to POST automatically. See https://linux.die.net/man/1/curl – pinoyyid Jul 17 '18 at 10:22
  • Multiple -d args have the same effect as a single -d arg with a concatenated string. See linux.die.net/man/1/curl – pinoyyid Jul 17 '18 at 11:22
  • Tests I've run use a single -d argument with key/values pairs delimited by &s as well as separate -d args (should make no difference), quoted & unquoted values for the -d argument (typically makes no difference unless there are spaces), raw values, escaped values, and uri-encoded values. The fact that the error I'm getting points explicitly to "invalid_grant" (which never changes, quoted, escaped etc) suggests the arguments are getting through in all cases, and that it's not the curl syntax per se but something else. – WhatsYourFunction Jul 17 '18 at 13:35
  • Perhaps this is a clue: I tried to run Step 1 using each of the 9 scopes listed at https://developers.google.com/drive/api/v3/about-auth. Only one, _drive.file_, returned an authorization. All the rest returned `{"error": "invalid_scope"}`. I'm not aware of any controls in Google API admin that would make the difference here -- perhaps "roles"? (https://console.developers.google.com/iam-admin/roles), but considering the `client_id` being used is the "owner" you'd think that gives access to all Google API scopes. – WhatsYourFunction Jul 17 '18 at 14:05
  • try and get the code using the browser window instead of your curl script. additional scopes need to be separated by a space. drive.file is the main one it gives you access to everything – Linda Lawton - DaImTo Jul 17 '18 at 14:06
  • If you're referring to the API explorer (https://developers.google.com/apis-explorer) -- that works perfectly. – WhatsYourFunction Jul 17 '18 at 14:15
  • If, on the other hand, we use the GET request that returns from the API Explorer (https://www.googleapis.com/drive/v3/files?key={YOUR_API_KEY}) and supply that API key in a browser, the return is `{ "error": { "errors": [ { "domain": "global", "reason": "insufficientFilePermissions", "message": "The user does not have sufficient permissions for this file." } ], "code": 403, "message": "The user does not have sufficient permissions for this file." }}` That suggests the GET request + API key isn't enough, hence the need for permissions, tokens, etc. using cURL. – WhatsYourFunction Jul 17 '18 at 14:22
  • acess_token= not key= is for public data using an API key – Linda Lawton - DaImTo Jul 17 '18 at 14:36
  • check my step one put that into your favorite browser and get the authorization code that way – Linda Lawton - DaImTo Jul 17 '18 at 14:38
  • Thanks DaImTo. Yes that works. BUT the 'approval code' that returns is temporary (correct?), and it wouldn't be practical to have users get their authorization through a browser. So how to run your Step 1 as cURL? Sent as a GET request it returns an HTML response claiming `Error 400: invalid_request Required parameter is missing: response_type` (it's there). Sent as POST the response is empty. – WhatsYourFunction Jul 17 '18 at 15:38
  • yeas the authorization code only works once and you have to use it within three minutes. Then you do the second call which i have also shown that returns an access token and a refresh token. Access token is used to call the api refresh token is used to get you a new access token when the refresh token expires read though this it might help you understand what you are trying to do here https://www.daimto.com/google-3-legged-oauth2-flow/ – Linda Lawton - DaImTo Jul 17 '18 at 15:40
  • Yes, thanks again. Your posts are clear and super helpful (for years!!) The key is to do that first step **without** a browser, presumably using cURL. Any clues there? – WhatsYourFunction Jul 17 '18 at 16:15
  • you have to put it in a browser but if I have time I will play with it in the morning – Linda Lawton - DaImTo Jul 17 '18 at 17:11
1

ANSWER:

A partial answer to the question -- 'partial' because it doesn't use pure cURL-- but it was possible to get it working using PHP to prep some of the dynamic and SSL values and then run cURL from within PHP.

The key reference for the following comes from Google's documentation here: https://developers.google.com/identity/protocols/OAuth2ServiceAccount

echo GoogleTokenRequest();

function GoogleTokenRequest(){
    $GoogleApiKeyInfo=GoogleApiKeyInfo();
    $Header=array();
    $Header["alg"]="RS256";
    $Header["typ"]="JWT";
    $ClaimSet=array();
    $ClaimSet["iss"]=$GoogleApiKeyInfo["client_email"];
    $ClaimSet["scope"]="https://www.googleapis.com/auth/drive";
    $ClaimSet["aud"]="https://www.googleapis.com/oauth2/v4/token";
    $ClaimSet["iat"]=time();
    $ClaimSet["exp"]=$ClaimSet["iat"]+(60);
    $Jws=base64_encode(json_encode($Header)).".".base64_encode(json_encode($ClaimSet));
    $OpenSslRslts=openssl_sign($Jws,$Signature,$GoogleApiKeyInfo["private_key"],OPENSSL_ALGO_SHA256);//Ref: http://php.net/manual/en/function.openssl-sign.php
    $Jwt=$Jws.".".base64_encode($Signature);
    $SendVars=array();
    $SendVars["grant_type"]=("urn:ietf:params:oauth:grant-type:jwt-bearer");
    $SendVars["assertion"]=$Jwt;
    $SendVars=http_build_query($SendVars);
    $UrlDomain="www.googleapis.com";
    $UrlPath="/oauth2/v4/token";
    $UrlFull=$UrlDomain.$UrlPath;
    $CurlType="Post";
    $ThisHeader=[
        strtoupper($CurlType)." ".$UrlPath,
        "Host: ".$UrlDomain,
        "Content-Type: application/x-www-form-urlencoded"
    ];
    return Process_cURL("https://".$UrlFull,$CurlType,$ThisHeader,$SendVars);
}

function GoogleApiKeyInfo(){
    //The following JSON data is downloaded from https://console.developers.google.com/apis/credentials when creating a project API key.
    return json_decode('{
    "type": "service_account",
    "project_id": "XXX",
    "private_key_id": "XXXX",
    "private_key": "-----BEGIN PRIVATE KEY-----VeryLongMultiLineString-----END PRIVATE KEY-----\n",
    "client_email": "xxxx@xxxx.iam.gserviceaccount.com",
    "client_id": "XXXXX",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://accounts.google.com/o/oauth2/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/xxxx@xxxx.iam.gserviceaccount.com"
    }');
}

function Process_cURL($ThisUrl,$ThisType,$ThisHeader,$ThisData){
    //Handler for a variety of cURL calls. Returns JSON string
}
WhatsYourFunction
  • 621
  • 1
  • 9
  • 25
  • 1
    You are my lifesaver! I spend more than 20 hours to find this nice and pure, no library, solution. – Artur Jun 06 '19 at 00:23