{"id":615,"date":"2018-06-13T12:35:30","date_gmt":"2018-06-13T10:35:30","guid":{"rendered":"http:\/\/edba.xyz\/?p=615"},"modified":"2020-11-23T11:59:20","modified_gmt":"2020-11-23T11:59:20","slug":"select-a-google-drive-file-with-picker","status":"publish","type":"post","link":"https:\/\/morillasweb.com\/index.php\/2018\/06\/13\/select-a-google-drive-file-with-picker\/","title":{"rendered":"Select a Google Drive file with picker"},"content":{"rendered":"<p>The use of Google picker to select a file is very easy, documentation is <a href=\"https:\/\/developers.google.com\/picker\/\">here<\/a>. One problem I have encountered is related with authorization.<\/p>\n<p>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.<\/p>\n<p>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 <a href=\"https:\/\/developers.google.com\/identity\/sign-in\/web\/server-side-flow\">here<\/a>.<\/p>\n<p>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 <em>offline<\/em>. Thus can be done by the regular sing-in functions:<\/p>\n<ul>\n<li><a href=\"https:\/\/developers.google.com\/identity\/sign-in\/web\/reference#googleauthgrantofflineaccessoptions\">GoogleAuth.grantOfflineAccess()<\/a>.<\/li>\n<li><a href=\"https:\/\/developers.google.com\/identity\/sign-in\/web\/reference#googleusergrantofflineaccessoptions\">GoogleUser.grantOfflineAccess()<\/a>.<\/li>\n<\/ul>\n<p>Summarizing, we need both in client side: access_token (for the picker), code (for the server processing of the file).<\/p>\n<p>Instead, if we use <a href=\"https:\/\/developers.google.com\/identity\/sign-in\/web\/reference#advanced\">advanced<\/a> Google Sign-In methods, we can use:<\/p>\n<ul>\n<li><a href=\"https:\/\/developers.google.com\/identity\/sign-in\/web\/reference#gapiauth2authorizeparams-callback\">gapi.auth2.authorize<\/a>().<\/li>\n<\/ul>\n<p>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.<\/p>\n<p>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).<\/p>\n<p>The tricky, in order to be asked only once for the authorization is the use of\u00a0<a href=\"https:\/\/developers.google.com\/identity\/sign-in\/web\/reference#googleuserreloadauthresponse\">reloadAuthResponse()<\/a>.<\/p>\n<p>The code how I resolved is here:<\/p>\n<p>file: picker1.html<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">&lt;html lang=\"en\"&gt;\n\t&lt;head&gt;\n\t\t&lt;script src=\"https:\/\/apis.google.com\/js\/platform.js?onload=start\" async defer&gt;&lt;\/script&gt;\t\t\n\t\t&lt;script src=\"https:\/\/ajax.googleapis.com\/ajax\/libs\/jquery\/1.8.2\/jquery.min.js\"&gt;&lt;\/script&gt;\n\t&lt;\/head&gt;\n\t&lt;body&gt;\n\t\t&lt;button id=\"signOut\" style=\"float:left; margin-right: 10px; display:hidden;\"&gt;Sign Out&lt;\/button&gt;\n\t\t&lt;div id=\"GoogleID\" style=\"padding-top: 2px;\"&gt;Connecting ...&lt;\/div&gt;\n\t\t&lt;p&gt;\n\t\t&lt;button id=\"pickFileButton\" style=\"height: 35px; float: left; margin-right: 10px;\"&gt;\n\t\t\t&lt;img src=\"Google_Drive_Logo.svg\" style=\"width:28px; height:28px; float:left;\"&gt;\n\t\t\t&lt;p style=\"display: inline-block; margin-top: 8px; margin-left: 8px; margin-right: 5px;\"&gt;Select File&lt;\/p&gt;\n\t\t&lt;\/button&gt;\t\t\n\t\t&lt;div id=\"fileID\" style=\"padding-top: 10px\"&gt;Not file selected&lt;\/div&gt;\n\t\t&lt;\/p&gt;\n\t\t\n\t&lt;\/body&gt;\n\t&lt;script&gt;\n\t\tvar GoogleAuth;\n\t\tvar googleUser;\n\t\tvar access_token;\n\t\tvar id_token;\n\t\tvar code_token;\n\t\tvar clientID = '774977077017-foo01allufp02gh33plbntv8sq2oalvv.apps.googleusercontent.com';\n\t\tfunction start() {\t\t\t\n\t\t\tgapi.load('auth2', function() {\t\t\t\t\t\t\n\t\t\t\tauth2 = gapi.auth2.init({\n\t\t\t\t\tclient_id: clientID,\n\t\t\t\t\t\/\/ Scopes to request in addition to 'profile' and 'email'\n\t\t\t\t\tscope: 'https:\/\/www.googleapis.com\/auth\/drive.readonly'\n\t\t\t\t}).then(function() {\n\t\t\t\t\tGoogleAuth = gapi.auth2.getAuthInstance();\n\t\t\t\t\tGoogleAuth.isSignedIn.listen(updateSigninStatus);\n\t\t\t\t\tif (GoogleAuth.isSignedIn.get()) {\t\t\/\/User is signed\t\t\t\t\t\t\n\t\t\t\t\t\tgoogleUser = GoogleAuth.currentUser.get();\t\t\t\t\t\t\n\t\t\t\t\t\t\/\/access_token = googleUser.getAuthResponse(true).access_token;\t\t\t\t\t\t\n\t\t\t\t\t\t$('#signOut').show();\n\t\t\t\t\t\t$('#GoogleID').html('Connected as: ' + googleUser.getBasicProfile().getName());\n\t\t\t\t\t\t\/\/console.log(googleUser.getAuthResponse(true));\n\t\t\t\t\t} else {\t\/\/ User is not signed in. Start Google auth flow when click button\n\t\t\t\t\t\t$('#GoogleID').html('Not connected');\n\t\t\t\t\t\t$('#signOut').hide();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\n\t\t\t});\n    \t}\n\t\tfunction updateSigninStatus() {\n\t\t\tconsole.log('Status changed');\n\t\t}\n\t\t\n\t\t$('#signOut').click(function() {\t\t\t\n\t\t\tGoogleAuth.disconnect().then(function () {\n\t\t\t\t$('#GoogleID').html('Not connected');\n\t\t\t\t$('#fileID').html('Not file selected');\n\t\t\t\t$('#signOut').hide();\n\t\t\t});\n\t\t});\n\t\t\n\t\t$('#pickFileButton').click(function() {\t\t\n\t\t\tif (typeof GoogleAuth === 'undefined') {     \/\/ the variable is undefined which implies auth2 has not been loaded\n\t\t\t\tconsole.log('Error: auth2 has not been loaded');\n\t\t\t\treturn;\n\t\t\t}\t\t\n\t\t\t\n\t\t\tGoogleAuth.grantOfflineAccess({\n\t\t\t\t\/\/prompt:'consent',\n\t\t\t}).then(\n\t\t\t\tfunction(resp) {\n\t\t\t\t\tcode = resp.code;\t\t\t\t\t\t\n\t\t\t\t\tgoogleUser = GoogleAuth.currentUser.get();\n\t\t\t\t\t$('#signOut').show();\n\t\t\t\t\t$('#GoogleID').html('Connected as: ' + googleUser.getBasicProfile().getName());\n\t\t\t\t\tgoogleUser.reloadAuthResponse().then(\n\t\t\t\t\t\tfunction(authResponse) {\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\taccess_token = authResponse.access_token;\n\t\t\t\t\t\t\tid_token = authResponse.id_token;\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tcreatePicker();\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t);\t\t\t\n\t\t});\t\t\n\t\t\n\t\tfunction createPicker() {\t\t\n\t\t\tconsole.log('code: '+code);\n\t\t\tconsole.log('access_token: '+access_token);\n\t\t\tconsole.log('id_token: '+id_token);\n\t\t\t\n\t\t\tgapi.load('picker', function() {\t\t\t\t\n\t\t\t\tvar picker = new google.picker.PickerBuilder().\n\t\t\t\t\taddView(google.picker.ViewId.DOCS).\n\t\t\t\t\tsetOAuthToken(access_token).\n\t\t\t\t\t\/\/setDeveloperKey('AIzaSyDM9wKd7gtBJZq64hgUHlO_XgZtwluEftk').\n\t\t\t\t\tsetCallback(pickerCallback).\n\t\t\t\t\tbuild();\n\t\t\t\tpicker.setVisible(true);\n\t\t\t});\n\t\t}\n\t\t\n\t\tfunction pickerCallback(data) {\t\t\t\n\t\t\tvar url = 'nothing';\n\t\t\tif (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {\n\t\t\t\tvar doc = data[google.picker.Response.DOCUMENTS][0];\n\t\t\t\tfileUrl = doc[google.picker.Document.URL];\n\t\t\t\tfileId = doc[google.picker.Document.ID];\t\t\t\t\n\t\t\t\tfileName = doc[google.picker.Document.NAME];\n\t\t\t\t$('#fileID').html('File: ' + fileName);\t\t\t\t\n\t\t\t\tvar data = 'code='+code+'&amp;fileId='+fileId;\n\t\t\t\t$.ajax({\n\t\t\t\t\ttype: 'POST',\n\t\t\t\t\turl: 'https:\/\/edba.xyz\/tutorials\/test2.php',\n\t\t\t\t\t\/\/ Always include an `X-Requested-With` header in every AJAX request to protect against CSRF attacks.\n\t\t\t\t\theaders: {'X-Requested-With': 'XMLHttpRequest'},\n\t\t\t\t\tcontentType: 'application\/octet-stream; charset=utf-8',\n\t\t\t\t\tprocessData: false,\n\t\t\t\t\tdata: data,\n\t\t\t\t\t\/\/dataType: \"json\"\n\t\t\t\t}).done(function(result) {\t\t\t\t\t\n\t\t\t\t\tconsole.log('exchanged access_token: '+result);\n\t\t\t\t\t\/\/ Handle or verify the server response.\n\t\t\t\t});\t\t\t\t\n\t\t\t}\t\t\n\t\t}\t\t\n\t&lt;\/script&gt;\n&lt;\/html&gt;<\/pre>\n<p>In the server it must be exchanged the code for an access_token. The code is here:<\/p>\n<p>file: picker1.php<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">&lt;?php\nini_set('display_errors', 'On');\nerror_reporting(E_ALL);\n\n$code = urlencode(explode(\"=\",explode(\"&amp;\", file_get_contents(\"php:\/\/input\"))[0])[1]);\n$fileId = explode(\"=\",explode(\"&amp;\", file_get_contents(\"php:\/\/input\"))[1])[1];\n\n$client_id = '774977077017-foo01allufp02gh33plbntv8sq2oalvv.apps.googleusercontent.com';\n$client_secret = 'HotSFRmLEfrt3vvn971uftnh';\n\n$url = 'https:\/\/www.googleapis.com\/oauth2\/v4\/token';\n$redirect_uri = 'https:\/\/edba.xyz';\n$data = array(\n\t'code' =&gt; $code,\n\t'client_id' =&gt; $client_id,\n\t'client_secret' =&gt; $client_secret,\n\t'redirect_uri' =&gt; $redirect_uri,\n\t'grant_type' =&gt; 'authorization_code'\t\t\t\t\t\t\n);\n$content = urldecode(http_build_query($data));\t\n\/\/You should get the string in the same form than: $content = 'code='.$code.'&amp;client_id='.$client_id.'&amp;client_secret='.$client_secret.'&amp;redirect_uri='.$redirect_uri.'&amp;grant_type=authorization_code';\n$headers = array(\n\t'Content-Type: application\/x-www-form-urlencoded',\n\t'Content-Type: application\/json',\n\t'Content-Length: '.strlen($content)\n);\n\n\/\/ use key 'http' even if you send the request to https:\/\/...\n$options = array(\n\t'http' =&gt; array(\t\t\n\t\t'header'  =&gt; $headers,\t\/\/ When php is compiled --with-curlwrappers\n\t\t\/\/'header'  =&gt; implode('\\r\\n', $headers).'\\r\\n',\t\/\/ \/\/ When php is not compiled --with-curlwrappers\n\t\t'method'  =&gt; 'POST',\n\t\t'content' =&gt; $content\n\t)\n);\n$context  = stream_context_create($options);\n$result = json_decode(file_get_contents($url, false, $context));\n\n\/\/ If you want to do the same call with cURL\n\/*\n$ch = curl_init();\n\t\/\/set the url, number of POST vars, POST data\n\tcurl_setopt($ch,CURLOPT_URL,$url);\n\tcurl_setopt($ch,CURLOPT_HTTPHEADER, $headers);\n\tcurl_setopt($ch,CURLOPT_CUSTOMREQUEST, \"POST\");\n\tcurl_setopt($ch,CURLOPT_POST,count($data));\n\tcurl_setopt($ch,CURLOPT_POSTFIELDS,$content);\n\t\/\/execute post\n\t$result = json_decode(curl_exec($ch));\n\tvar_dump ($result);\n*\/\n\n$access_token = $result-&gt;access_token;\necho $access_token;\n\n?&gt;<\/pre>\n<p>&nbsp;<\/p>\n<p>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The use of Google picker to select a file is very easy, documentation is here. One problem I have encountered<\/p>\n<p><a href=\"https:\/\/morillasweb.com\/index.php\/2018\/06\/13\/select-a-google-drive-file-with-picker\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\">Select a Google Drive file with picker<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[23],"tags":[],"class_list":["post-615","post","type-post","status-publish","format-standard","hentry","category-google-sheets"],"_links":{"self":[{"href":"https:\/\/morillasweb.com\/index.php\/wp-json\/wp\/v2\/posts\/615","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/morillasweb.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/morillasweb.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/morillasweb.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/morillasweb.com\/index.php\/wp-json\/wp\/v2\/comments?post=615"}],"version-history":[{"count":1,"href":"https:\/\/morillasweb.com\/index.php\/wp-json\/wp\/v2\/posts\/615\/revisions"}],"predecessor-version":[{"id":925,"href":"https:\/\/morillasweb.com\/index.php\/wp-json\/wp\/v2\/posts\/615\/revisions\/925"}],"wp:attachment":[{"href":"https:\/\/morillasweb.com\/index.php\/wp-json\/wp\/v2\/media?parent=615"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/morillasweb.com\/index.php\/wp-json\/wp\/v2\/categories?post=615"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/morillasweb.com\/index.php\/wp-json\/wp\/v2\/tags?post=615"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}