Select a Google Drive file with picker

The use of Google picker to select a file is very easy, documentation is here. One problem I have encountered is related with authorization.

For asking for permisson to select a file (with picker) you need the access_token provided in the client side. But then, you may need to process the file in server side, so you probably need to send the access_token to the server.

We have seen that passing access_token from client to the server is not secure (at least you have to do with https), and Google recommends Sign-In server side flow as documented here.

In order to do that, you need the one-time code for exchanging in server side for an access_token. The only way to do that with javascript libraries is by getting permission from the user to access the specified scopes offline. Thus can be done by the regular sing-in functions:

Summarizing, we need both in client side: access_token (for the picker), code (for the server processing of the file).

Instead, if we use advanced Google Sign-In methods, we can use:

This way, we can get both tokens at one time. But this is not the recomended way if you plan to use authorization more than one time.

In the recommended way, you should authorize twice: for the initial sig-in (for get the access_token) and for the offline access (to get the exchangable code).

The tricky, in order to be asked only once for the authorization is the use of reloadAuthResponse().

The code how I resolved is here:

file: picker1.html

<html lang="en">
	<head>
		<script src="https://apis.google.com/js/platform.js?onload=start" async defer></script>		
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
	</head>
	<body>
		<button id="signOut" style="float:left; margin-right: 10px; display:hidden;">Sign Out</button>
		<div id="GoogleID" style="padding-top: 2px;">Connecting ...</div>
		<p>
		<button id="pickFileButton" style="height: 35px; float: left; margin-right: 10px;">
			<img src="Google_Drive_Logo.svg" style="width:28px; height:28px; float:left;">
			<p style="display: inline-block; margin-top: 8px; margin-left: 8px; margin-right: 5px;">Select File</p>
		</button>		
		<div id="fileID" style="padding-top: 10px">Not file selected</div>
		</p>
		
	</body>
	<script>
		var GoogleAuth;
		var googleUser;
		var access_token;
		var id_token;
		var code_token;
		var clientID = '774977077017-foo01allufp02gh33plbntv8sq2oalvv.apps.googleusercontent.com';
		function start() {			
			gapi.load('auth2', function() {						
				auth2 = gapi.auth2.init({
					client_id: clientID,
					// Scopes to request in addition to 'profile' and 'email'
					scope: 'https://www.googleapis.com/auth/drive.readonly'
				}).then(function() {
					GoogleAuth = gapi.auth2.getAuthInstance();
					GoogleAuth.isSignedIn.listen(updateSigninStatus);
					if (GoogleAuth.isSignedIn.get()) {		//User is signed						
						googleUser = GoogleAuth.currentUser.get();						
						//access_token = googleUser.getAuthResponse(true).access_token;						
						$('#signOut').show();
						$('#GoogleID').html('Connected as: ' + googleUser.getBasicProfile().getName());
						//console.log(googleUser.getAuthResponse(true));
					} else {	// User is not signed in. Start Google auth flow when click button
						$('#GoogleID').html('Not connected');
						$('#signOut').hide();
					}
				});
			
			});
    	}
		function updateSigninStatus() {
			console.log('Status changed');
		}
		
		$('#signOut').click(function() {			
			GoogleAuth.disconnect().then(function () {
				$('#GoogleID').html('Not connected');
				$('#fileID').html('Not file selected');
				$('#signOut').hide();
			});
		});
		
		$('#pickFileButton').click(function() {		
			if (typeof GoogleAuth === 'undefined') {     // the variable is undefined which implies auth2 has not been loaded
				console.log('Error: auth2 has not been loaded');
				return;
			}		
			
			GoogleAuth.grantOfflineAccess({
				//prompt:'consent',
			}).then(
				function(resp) {
					code = resp.code;						
					googleUser = GoogleAuth.currentUser.get();
					$('#signOut').show();
					$('#GoogleID').html('Connected as: ' + googleUser.getBasicProfile().getName());
					googleUser.reloadAuthResponse().then(
						function(authResponse) {								
							access_token = authResponse.access_token;
							id_token = authResponse.id_token;								
							createPicker();
						}
					);
				}
			);			
		});		
		
		function createPicker() {		
			console.log('code: '+code);
			console.log('access_token: '+access_token);
			console.log('id_token: '+id_token);
			
			gapi.load('picker', function() {				
				var picker = new google.picker.PickerBuilder().
					addView(google.picker.ViewId.DOCS).
					setOAuthToken(access_token).
					//setDeveloperKey('AIzaSyDM9wKd7gtBJZq64hgUHlO_XgZtwluEftk').
					setCallback(pickerCallback).
					build();
				picker.setVisible(true);
			});
		}
		
		function pickerCallback(data) {			
			var url = 'nothing';
			if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
				var doc = data[google.picker.Response.DOCUMENTS][0];
				fileUrl = doc[google.picker.Document.URL];
				fileId = doc[google.picker.Document.ID];				
				fileName = doc[google.picker.Document.NAME];
				$('#fileID').html('File: ' + fileName);				
				var data = 'code='+code+'&fileId='+fileId;
				$.ajax({
					type: 'POST',
					url: 'https://edba.xyz/tutorials/test2.php',
					// Always include an `X-Requested-With` header in every AJAX request to protect against CSRF attacks.
					headers: {'X-Requested-With': 'XMLHttpRequest'},
					contentType: 'application/octet-stream; charset=utf-8',
					processData: false,
					data: data,
					//dataType: "json"
				}).done(function(result) {					
					console.log('exchanged access_token: '+result);
					// Handle or verify the server response.
				});				
			}		
		}		
	</script>
</html>

In the server it must be exchanged the code for an access_token. The code is here:

file: picker1.php

<?php
ini_set('display_errors', 'On');
error_reporting(E_ALL);

$code = urlencode(explode("=",explode("&", file_get_contents("php://input"))[0])[1]);
$fileId = explode("=",explode("&", file_get_contents("php://input"))[1])[1];

$client_id = '774977077017-foo01allufp02gh33plbntv8sq2oalvv.apps.googleusercontent.com';
$client_secret = 'HotSFRmLEfrt3vvn971uftnh';

$url = 'https://www.googleapis.com/oauth2/v4/token';
$redirect_uri = 'https://edba.xyz';
$data = array(
	'code' => $code,
	'client_id' => $client_id,
	'client_secret' => $client_secret,
	'redirect_uri' => $redirect_uri,
	'grant_type' => 'authorization_code'						
);
$content = urldecode(http_build_query($data));	
//You should get the string in the same form than: $content = 'code='.$code.'&client_id='.$client_id.'&client_secret='.$client_secret.'&redirect_uri='.$redirect_uri.'&grant_type=authorization_code';
$headers = array(
	'Content-Type: application/x-www-form-urlencoded',
	'Content-Type: application/json',
	'Content-Length: '.strlen($content)
);

// use key 'http' even if you send the request to https://...
$options = array(
	'http' => array(		
		'header'  => $headers,	// When php is compiled --with-curlwrappers
		//'header'  => implode('\r\n', $headers).'\r\n',	// // When php is not compiled --with-curlwrappers
		'method'  => 'POST',
		'content' => $content
	)
);
$context  = stream_context_create($options);
$result = json_decode(file_get_contents($url, false, $context));

// If you want to do the same call with cURL
/*
$ch = curl_init();
	//set the url, number of POST vars, POST data
	curl_setopt($ch,CURLOPT_URL,$url);
	curl_setopt($ch,CURLOPT_HTTPHEADER, $headers);
	curl_setopt($ch,CURLOPT_CUSTOMREQUEST, "POST");
	curl_setopt($ch,CURLOPT_POST,count($data));
	curl_setopt($ch,CURLOPT_POSTFIELDS,$content);
	//execute post
	$result = json_decode(curl_exec($ch));
	var_dump ($result);
*/

$access_token = $result->access_token;
echo $access_token;

?>

 

.