Google OAuth2 and app script in IFRAME sandbox - javascript

I am a newbie as far as web development is concerned and even more so with Google App Scripts and OAuth2.0. Having said that, I have researched enough and also tried several tricks, but still can't get past this issue.
I borrowed sample from here:
Google Developers - Client API Library
Then created an Apps Script project with an index.html file with code from that page. I also created a project on the developer console, created a client ID, API key and turned on the required API support. I also made the required changes to the sample to reflect the new client ID and API key.
The index.html page is served from HTML Service with SandBox Mode set to IFRAME. If I load the URL in a browser window (say using incognito mode) and click "Authorize" button, it opens the Google sign-in window. But after signing in, it opens two new tabs with messages
Please close this window
and the original browser window shows no change.
The JavaScript console shows error messages like these:
Unsafe JavaScript attempt to initiate navigation for frame with URL ''
from frame with URL
https://accounts.google.com/o/oauth2/postmessageRelay?parent=https%3A%2F%2F…6lxdpyio6iqy-script.googleusercontent.com#rpctoken=288384029&forcesecure=1.
The frame attempting navigation is sandboxed, and is therefore
disallowed from navigating its ancestors.
From the messages, it seems its an effect of using IFRAME and some sort of security feature is preventing the callback being delivered to the original window. If I reload the original window, things work OK. But that's not what I would ideally like.
How do I work around this issue? Its a very simple project and I can provide source code if that helps.
Thanks,
Pavan
Edit: Here is the sample code I'm trying. You would need to have your client ID and API Key and also set JS origins in the Google Console for things to work:
Code.gs
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('index').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
index.html
<!--
Copyright (c) 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
To run this sample, replace YOUR API KEY with your application's API key.
It can be found at https://code.google.com/apis/console/?api=plus under API Access.
Activate the Google+ service at https://code.google.com/apis/console/ under Services
-->
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
</head>
<body>
<!--Add a button for the user to click to initiate auth sequence -->
<button id="authorize-button" style="visibility: hidden">Authorize</button>
<script type="text/javascript">
// Enter a client ID for a web application from the Google Developer Console.
// The provided clientId will only work if the sample is run directly from
// https://google-api-javascript-client.googlecode.com/hg/samples/authSample.html
// In your Developer Console project, add a JavaScript origin that corresponds to the domain
// where you will be running the script.
var clientId = 'YOUR_CLIENT_ID';
// Enter the API key from the Google Develoepr Console - to handle any unauthenticated
// requests in the code.
// The provided key works for this sample only when run from
// https://google-api-javascript-client.googlecode.com/hg/samples/authSample.html
// To use in your own application, replace this API key with your own.
var apiKey = 'YOUR API KEY';
// To enter one or more authentication scopes, refer to the documentation for the API.
var scopes = 'https://www.googleapis.com/auth/plus.me';
// Use a button to handle authentication the first time.
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true, response_type: 'token'}, handleAuthResult);
}
function handleAuthResult(authResult) {
var authorizeButton = document.getElementById('authorize-button');
if (authResult && !authResult.error) {
authorizeButton.style.visibility = 'hidden';
makeApiCall();
} else {
authorizeButton.style.visibility = '';
authorizeButton.onclick = handleAuthClick;
}
}
function handleAuthClick(event) {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false, response_type: 'token'}, handleAuthResult);
return false;
}
// Load the API and make an API call. Display the results on the screen.
function makeApiCall() {
gapi.client.load('plus', 'v1', function() {
var request = gapi.client.plus.people.get({
'userId': 'me'
});
request.execute(function(resp) {
var heading = document.createElement('h4');
var image = document.createElement('img');
image.src = resp.image.url;
heading.appendChild(image);
heading.appendChild(document.createTextNode(resp.displayName));
heading.appendChild(document.createTextNode(resp.emails[0].value));
document.getElementById('content').appendChild(heading);
});
});
}
</script>
<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
<div id="content"></div>
<p>Retrieves your profile name using the Google Plus API.</p>
</body>
</html>

found a solution... not nice but works oO:
the trick is to remove the oauth2relay iframes before the auth window is closed. after the window is closed you have to add the frames again and do a immediate request, if that works the user authorized the app.
be careful:
this script does not check if the user meanwhile is logged out or the token is expired, as long as the webapp window is open the same token is used.
Code.js:
function doGet(e) {
return HtmlService.createTemplateFromFile('Index').evaluate().setTitle(formSettings.title).setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function include(file) {
return HtmlService.createHtmlOutputFromFile(file).getContent();
}
function doPost(meta) {
if (!meta || !meta.auth) {
throw new Error('not authorized');
return;
}
var auth = JSON.parse(UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + meta.auth.access_token, { muteHttpExceptions: true }).getContentText());
if (auth.error || !auth.email) {
throw new Error('not authorized');
return;
}
if (typeof this[meta.method + '_'] == 'function') {
return this[meta.method + '_'](auth.email, meta.data);
}
throw new Error('unknown method');
}
function test_(email, data) {
return email;
}
Index.html:
<html>
<head>
<?!= include('JavaScript'); ?>
</head>
<body>
<div class="content-wrapper">
</div>
</body>
</html>
Javascript.html:
<script type='text/javascript' src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type='text/javascript' src="//apis.google.com/js/client.js?onload=apiLoaded" async></script>
<script type='text/javascript'>
var clientId = '*************-********************************.apps.googleusercontent.com';
var scopes = ['https://www.googleapis.com/auth/plus.me', 'https://www.googleapis.com/auth/userinfo.email'];
var loaded = false;
var auth = null;
function apiLoaded() {
loaded = true;
login();
}
window._open = window.open;
window._windows = [];
window.open = function(url) {
var w = window._open.apply(window,arguments);
window._windows.push(w);
return w;
}
function login(step) {
step || (step = 0);
if (!loaded) {
return;
}
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: (step <= 0 || step >= 2) }, function(authResult) {
if (authResult) {
if (authResult.error) {
if (authResult.error == 'immediate_failed' && authResult.error_subtype == 'access_denied' && step <= 0) {
var interval = setInterval(function() {
var $ifr = $('iframe');//[id^=oauth2relay]');
if (!window._windows.length) {
clearInterval(interval);
return;
}
if ($ifr.length) {
clearInterval(interval);
$ifr.detach();
var w = window._windows.pop();
if (w) {
var interval2 = setInterval(function() {
if (w.closed) {
clearInterval(interval2);
$('body').append($ifr);
login(2);
}
});
} else {
$('body').append($ifr);
}
}
},500);
login(1);
} else if (authResult.error == 'immediate_failed' && authResult.error_subtype == 'access_denied' && step >= 2) {
//user canceled auth
} else {
//error
}
} else {
auth = authResult;
doPost('test', { some: 'data' }, 'test');
}
} else {
//error
}
});
}
function test() {
console.log(arguments);
}
//with this method you can do a post request to webapp server
function doPost(method, data, callbackName) {
data || (data = {});
google.script.run.withSuccessHandler(onSuccess).withFailureHandler(onError).withUserObject({ callback: callbackName }).doPost({ method: method, data: data, auth: auth });
}
function onSuccess(data, meta) {
if (typeof window[meta.callback] == 'function') {
window[meta.callback](null, data);
}
}
function onError(err, meta) {
if (typeof window[meta.callback] == 'function') {
window[meta.callback](err);
}
}
</script>

Related

File Picker Sidebar Communication Errors

I'm working on a standalone script that will eventually be published as an add-on. It will have a sidebar users interact with and from there they can launch the Google File Picker to upload a report for processing. I previously was looking for a way for the sidebar to know that the file picker was done. I replicated this answer successfully and got the File picker dialog to send a message back to the sidebar indicating it was finished. However, the console is full of some errors and warnings that I'm wondering if I should be concerned about. The errors are:
Unsafe attempt to initiate navigation for frame with origin
'https://docs.google.com' from frame with URL
'https://***.googleusercontent.com/userCodeAppPanel'.
The frame attempting navigation of the top-level window is sandboxed,
but the flag of 'allow-top-navigation' or
'allow-top-navigation-by-user-activation' is not set.
DOMException: Blocked a frame with origin
"https://###.googleusercontent.com"
from accessing a cross-origin frame.
at findSideBar (https://###.googleusercontent.com/userCodeAppPanel:80:18)
at w0.pickerCallback [as Fb] (https://###.googleusercontent.com/userCodeAppPanel:98:11)
at w0._.h.iV (https://apis.google.com/_/scs/apps-static/_/js/k=oz.gapi.en.uAzDleg2hnU.O/m=picker/rt=j/sv=1/d=1/ed=1/am=AQ/rs=AGLTcCMT6b3QcRI88QolvkcdUjC8YnoTvA/cb=gapi.loaded_0:740:393)
at d (https://apis.google.com/_/scs/apps-static/_/js/k=oz.gapi.en.uAzDleg2hnU.O/m=picker/rt=j/sv=1/d=1/ed=1/am=AQ/rs=AGLTcCMT6b3QcRI88QolvkcdUjC8YnoTvA/cb=gapi.loaded_0:215:143)
at b (https://apis.google.com/_/scs/apps-static/_/js/k=oz.gapi.en.uAzDleg2hnU.O/m=picker/rt=j/sv=1/d=1/ed=1/am=AQ/rs=AGLTcCMT6b3QcRI88QolvkcdUjC8YnoTvA/cb=gapi.loaded_0:210:1)
Refused to get unsafe header "X-HTTP-Status-Code-Override"
The first error I have seen before, however, the second is new and relevant to sending the message from the file picker dialog back to the sidebar.
It's worth mentioning that everything still works. My question is primarily are these errors I should be worried about, will they cause problems when the add-on is being reviewed before being published, and how can I correct them?
I've included below my code for the sidebar creation, picker creation, and the relevant code for sending the message from the picker to the sidebar.
Sidebar Creation:
function buildSidebar(type) {
hideColumns();
hideRows();
hideSheets();
if(type == 'setup' || checkSetup()) {
var html = HtmlService.createTemplateFromFile('SidebarSetupHTML')
.evaluate()
.setTitle('Test');
} else {
var html = HtmlService.createTemplateFromFile('SidebarMainHTML')
.evaluate()
.setTitle('Test');
}
SpreadsheetApp.getUi().showSidebar(html);
}
Picker Creation:
function showPicker() {
var html = HtmlService.createHtmlOutputFromFile('PickerHTML.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select Report(s)');
}
Picker Message Code:
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
(function findSideBar(limit) {
let f = window.top.frames;
for (let i = 0; i < limit; ++i) {
try {
if (
f[i] /*/iframedAppPanel*/ &&
f[i].length &&
f[i][0] && //#sandboxFrame
f[i][0][0] && //#userHtmlFrame
window !== f[i][0][0] //!== self
) {
console.info('Sidebar found');
var sidebar = f[i][0][0];
sidebar.modalDone('PICKED'); //Modal has finished
console.log('Message sent');
google.script.host.close();
}
} catch (e) {
console.error(e);
continue;
}
}
})(10);
}
}
Sidebar launch picker and receive message:
function testPicker() {
google.script.run.withSuccessHandler(pickerResponse).showPicker();
}
function pickerResponse(e) {
(async () => {
let receiver = new Promise((res, rej) => {
window.modalDone = res;
});
var message = await receiver;
console.log('message received');
if(message == 'PICKED' || message == "NOT_PICKED") {
console.log(message);
}
//Do what you want here
})();
}
When I saw your script, I thought that the reason for your issue might be the loop after the if statement was true. So for example, when the if statement is true, how about putting break in the last line? So, how about the following modification?
From:
if (
f[i] /*/iframedAppPanel*/ &&
f[i].length &&
f[i][0] && //#sandboxFrame
f[i][0][0] && //#userHtmlFrame
window !== f[i][0][0] //!== self
) {
console.info('Sidebar found');
var sidebar = f[i][0][0];
sidebar.modalDone('PICKED'); //Modal has finished
console.log('Message sent');
google.script.host.close();
}
To:
if (
f[i] /*/iframedAppPanel*/ &&
f[i].length &&
f[i][0] && //#sandboxFrame
f[i][0][0] && //#userHtmlFrame
window !== f[i][0][0] //!== self
) {
console.info('Sidebar found');
var sidebar = f[i][0][0];
sidebar.modalDone('PICKED'); //Modal has finished
console.log('Message sent');
google.script.host.close();
break; // <--- Added
}
Note:
When I tested a following sample script for a custom dialog on Google Docs (For example, it's Google Spreadsheet),
<script>
google.script.host.close();
alert("ok");
</script>
I confirmed that the alert window is opened and also, the dialog is closed. So I thought that google.script.host.close() might work with the asynchronous process.
For example, when google.script.run with the high process cost is used as follows,
<script>
google.script.host.close();
google.script.run.withSuccessHandler(_ => alert("ok2")).myFunction();
alert("ok1");
</script>
it seems that myFunction() and alert("ok2") are not run because the dialog is closed before run myFunction(), while alert("ok1") is run. On the other hand, when the process cost is low, it seems that the script after google.script.host.close() is run.
From these situation, as an attempt, I have proposed to add break after google.script.host.close() for OP's issue.
Reference:
Loops and iteration

Failing to export variable defined in function to another concurrently-ran JS file

I have been reading different posts on Google and SO, but I just can't figure out why this does not work.
There are 2 HTML and 2 JS files involved in this case (explanation given below in words after the code chunks).
1) index.html
<div id="center">
<img id="logo" src="../img/logowshadow.png" alt="logo">
<p id="para">Get your google slides!</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize-button" style="display: none;">Sign in</button>
<button id="signout-button" style="display: none;">Sign Out</button>
</div>
<script src="../js/homePage.js"></script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
2) homePage.js
var apiKey = 'AIzaSyCSjg3rrx6Obl4ngZsDlFlV4degUJSMvbw';
var discoveryDocs = ["https://slides.googleapis.com/$discovery/rest?version=v1"];
var clientId = '408869653199-ruoft30vmoqrgpku3us3qd2leb3k6tp1.apps.googleusercontent.com';
var scopes = 'https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/drive';
var authorizeButton = document.getElementById('authorize-button');
var signoutButton = document.getElementById('signout-button');
var user;
var authResponse;
var oauthToken;
var pickerApiLoaded = false;
var chosenPresentation = null;
function handleClientLoad() {
// Load the API client and auth2 library
gapi.load('client:auth2', initClient);
//Load the Picker API
gapi.load('picker', onPickerApiLoad);
}
function initClient() {
gapi.auth2.init({
apiKey: apiKey,
discoveryDocs: discoveryDocs,
clientId: clientId,
scope: scopes
}).then(function () {
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
// Set the current Google User
gapi.auth2.getAuthInstance().currentUser.listen(updateUser);
// Handle the initial sign-in state.
updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
});
}
// Callback to make sure that the Picker API has loaded
function onPickerApiLoad() {
pickerApiLoaded = true;
createPicker();
}
// Store the current Google user
function updateUser(gUser) {
user = gUser;
updateToken();
}
// Store the access token
function updateToken() {
authResponse = user.getAuthResponse(true);
oauthToken = authResponse.access_token;
}
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
authorizeButton.style.display = 'none';
signoutButton.style.display = 'block';
createPicker();
} else {
authorizeButton.style.display = 'block';
signoutButton.style.display = 'none';
}
}
function handleAuthClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
function handleSignoutClick(event) {
gapi.auth2.getAuthInstance().signOut();
}
// Create and render a Picker object for picking user slides
function createPicker() {
if (pickerApiLoaded && oauthToken) {
var picker = new google.picker.PickerBuilder().
addView(google.picker.ViewId.PRESENTATIONS).
setOAuthToken(oauthToken).
setDeveloperKey(apiKey).
setCallback(pickerCallback).
build();
picker.setVisible(true);
}
}
// Callback implementation
function pickerCallback(data) {
var url = 'nothing';
if(data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
url = doc[google.picker.Document.URL].replace('edit', 'present');
var item_name = doc[google.picker.Document.NAME];
alert('You picked ' + item_name);
//export the chosen presentation for use in mobileControl.js
chosenPresentation = doc[google.picker.Document.ID];
alert("chosen: " + chosenPresentation);
exports.chosenPresentation = chosenPresentation;
window.location.replace(url);
}
}
3) mobile.html
<div id="instructions">
<p>Swipe <b>left</b> to go to the previous slide.</p>
<p>Swipe <b>right</b> to go to the next slide.</p>
</div>
<script src="../js/mobileControl.js"></script>
and 4) mobileControl.js
alert("Loaded the JavaScript!"); //Shows up
var m = require('./homePage.js');
alert("imported"); //Does not show up
alert(m.chosenPresentation); //Does not show up
To put in words what my code is trying to achieve: users will be able to sign in to their google accounts, and select a set of Presentation found on their Google drive. I want to pass the Presentation ID on from homePage.js to mobileControl.js, and I attempted to do so using Node's exports. I suspect that it is not working because both scripts are run 'at the same time' (index.html is meant to run on the computer, while mobile.html runs on a mobile device...concurrently). But I am not sure if I am right in identifying the cause, and if so, is there a way to export the variable from within the function after it has been defined? Perhaps I should detect it when the slide has been selected, and only load mobilePage.js after everything in homePage.js has ran?
My apologies for the long read, but my previous attempt at diluting the example obviously failed miserably so...
Update: user #vsenko is totally right in saying that I have mixed up client-side programming and server-side programming, so do read up more on this if you are facing the same issue as me
As far as I can see, you are trying to load .js files with NodeJS specific API (RequireJS API to precise) directly on the web page. This is not going to work because browsers do not implement it natively. To utilize this API you will have to use a preprocessor (Webpack, Browserify or something else).
Other apparent problem with your approach is that you assume that it is possible to transfer data between different devices using something that look like NodeJS modules. But it is not possible, you will have transfer data over the network between your devices directly or though your server.

embed google drive picker as a part of page

I want to embed/implement Google Drive as a part of my page; like a normal grid or a table instead of having as a popup. I have took a reference from GoogleAPI page. Also, researched many things for my requirements but nothing would worked for me.
Here is the javascript code that I am using
// The Browser API key obtained from the Google API Console.
// Replace with your own Browser API key, or your own key.
var developerKey = 'xxxxxxxxxxxxxx';
// The Client ID obtained from the Google API Console. Replace with your own Client ID.
var clientId = "xxxxxxxxxxxx.apps.googleusercontent.com"
// Replace with your own project number from console.developers.google.com.
// See "Project number" under "IAM & Admin" > "Settings"
var appId = "xxxxxxxxxxxx";
// Scope to use to access user's Drive items.
var scope = ['https://www.googleapis.com/auth/drive'];
var pickerApiLoaded = false;
var oauthToken;
var picker;
// Use the Google API Loader script to load the google.picker script.
function loadPicker() {
gapi.load('auth', {
'callback': onAuthApiLoad
});
gapi.load('picker', {
'callback': onPickerApiLoad
});
}
function onAuthApiLoad() {
window.gapi.auth.authorize({
'client_id': clientId,
'scope': scope,
'immediate': false
},
handleAuthResult);
}
function onPickerApiLoad() {
pickerApiLoaded = true;
createPicker();
}
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
createPicker();
}
}
// Create and render a Picker object for searching images.
function createPicker() {
if (pickerApiLoaded && oauthToken) {
var view = new google.picker.DocsView()
.setIncludeFolders(true)
.setOwnedByMe(true);
picker = new google.picker.PickerBuilder()
.enableFeature(google.picker.Feature.NAV_HIDDEN)
.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
.setAppId(appId)
.setOAuthToken(oauthToken)
.addView(view)
.addView(new google.picker.DocsUploadView().setIncludeFolders(true))
.setDeveloperKey(developerKey)
.setCallback(pickerCallback)
.build();
picker.setVisible(true);
}
}
// A simple callback implementation.
function pickerCallback(data) {
if (data.action == google.picker.Action.PICKED) {
var fileId = data.docs[0].id;
alert('The user selected: ' + fileId);
}
}
<button onclick="loadPicker(); return false;">Pick From Google Drive</button>
<div id="result"></div>
<!-- The Google API Loader script. -->
<script type="text/javascript" src="https://apis.google.com/js/api.js"></script>
Use PickerBuilder.toUri() instead of PickerBuilder.build().
It will return picker url and set this to iframe.
According to the issue reported here,
gapi.auth is deprecated. You should use gapi.auth2 instead.
From Google Developers
Use,
gapi.auth2.init({
client_id: 'CLIENT_ID.apps.googleusercontent.com',
scope : scope ,
});
and it will return a Promise
gapi.auth2.GoogleAuth
A full reference can be seen in the Google Developer Page
It sounds like you want to use the Google Drive API rather than the picker API.
This allows you to query drive for files without using the GUI.
https://developers.google.com/drive/v3/web/quickstart/js
The example in this quickstart prints out a list of files from the authorized account onto the page.

Get access token of instagram API

I want to get the access token in order to diaply the images of an account. So I display a pop up where the user can connect. The pop up works but it redirects to instagram site, with the user connected instead of send me the code. The link to the connection is something like :
https://www.instagram.com/accounts/login/?force_classic_login=&next=/oauth/authorize/%3Fclient_id=aaaaaaaa&redirect_uri=url&response_type=token
I log in and then, it redirects me to :
https://www.instagram.com/oauth/authorize/?client_id=aaaaaaa&redirect_uri=url&response_type=token
I don't understand how I can get the code. And I also used the exact same code as : https://github.com/radykal/instagram-popup-login
Can someone help me please ?
EDIT
var loc = window.location.host+window.location.pathname;
var accessToken = null; //the access token is required to make any endpoint calls, http://instagram.com/developer/endpoints/
var authenticateInstagram = function(instagramClientId, instagramRedirectUri, callback) {
//the pop-up window size, change if you want
var popupWidth = 700,
popupHeight = 500,
popupLeft = (window.screen.width - popupWidth) / 2,
popupTop = (window.screen.height - popupHeight) / 2;
//the url needs to point to instagram_auth.php
var popup = window.open('instagram_auth.php', '', 'width='+popupWidth+',height='+popupHeight+',left='+popupLeft+',top='+popupTop+'');
popup.onload = function() {
//open authorize url in pop-up
if(window.location.hash.length == 0) {
popup.open('https://instagram.com/oauth/authorize/?client_id='+instagramClientId+'&redirect_uri='+instagramRedirectUri+'&response_type=token', '_self');
}
//an interval runs to get the access token from the pop-up
var interval = setInterval(function() {
try {
console.log(window.location);
//check if hash exists
if(popup.location.hash.length) {
//hash found, that includes the access token
clearInterval(interval);
accessToken = popup.location.hash.slice(14); //slice #access_token= from string
popup.close();
if(callback != undefined && typeof callback == 'function') callback();
}
}
catch(evt) {
//permission denied
console.log("error");
}
}, 100);
}
};
function login_callback() {
alert("You are successfully logged in! Access Token: "+accessToken);
}
function login() {
authenticateInstagram(
'16edb5c3bc05437594d69178f2aa646a', //instagram client ID
'localhost/facebook', //instagram redirect URI
login_callback //optional - a callback function
);
return false;
}
The code is ok, I think it is a problem with your app settings: Login to Instagram Developer, go to "Manage Client" and the "security" tab an disable "Implicit OAuth".

Google picker auth popup is being blocked

I have a mobile site which lists jobs, the user applies and uploads their CV (resume) - I want them to be able to choose a file from their Google Drive.
I've created the Hello world example here - https://developers.google.com/picker/docs/ (code reproduced here for convenience)
Problem is that if not already logged into Drive, a popup to login is launched. This is bad enough on a desktop but really bad on a phone.
I have tried this solution, but get 'TypeError: gapi.auth is undefined'
I also tried launching the picker from an onclick event rather than the onload as described by the docs.
function launchDrive()
{
gapi.load('auth', {'callback': onAuthApiLoad});
gapi.load('picker', {'callback': onPickerApiLoad});
}
<input type='button' value='Launch Drive' onclick='launchDrive();'>
Sample Google code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Google Picker Example</title>
<script type="text/javascript">
var developerKey = 'xxxxxxxYYYYYYYY-12345678';
var clientId = "1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com"
var scope = ['https://www.googleapis.com/auth/photos'];
var pickerApiLoaded = false;
var oauthToken;
function onApiLoad() {
gapi.load('auth', {'callback': onAuthApiLoad});
gapi.load('picker', {'callback': onPickerApiLoad});
}
function onAuthApiLoad() {
window.gapi.auth.authorize(
{
'client_id': clientId,
'scope': scope,
'immediate': false
},
handleAuthResult);
}
function onPickerApiLoad() {
pickerApiLoaded = true;
createPicker();
}
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
createPicker();
}
}
// Create and render a Picker object for picking user Photos.
function createPicker() {
if (pickerApiLoaded && oauthToken) {
var picker = new google.picker.PickerBuilder().
addView(google.picker.ViewId.PHOTOS).
setOAuthToken(oauthToken).
setDeveloperKey(developerKey).
setCallback(pickerCallback).
build();
picker.setVisible(true);
}
}
// A simple callback implementation.
function pickerCallback(data) {
var url = 'nothing';
if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
url = doc[google.picker.Document.URL];
}
var message = 'You picked: ' + url;
document.getElementById('result').innerHTML = message;
}
</script>
</head>
<body>
<div id="result"></div>
<!-- The Google API Loader script. -->
<script type="text/javascript" src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
13 May 2015 edit
Further to Jason's answer, here is what I also tried (called by a button oncllick):
function launchDrive()
{
//gapi.load('auth', {'callback': onAuthApiLoad});
gapi.auth.init(onAuthApiLoad);
gapi.load('picker', {'callback': onPickerApiLoad});
}
You will want to call gapi.auth.init. See the docs here: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauthinit
Initializes the authorization feature. Call this when the client loads to prevent popup blockers from blocking the auth window on gapi.auth.authorize calls.
To solve your issue you need to understand how google performs oauth in your case:
gapi performs init actions
gapi opens a google auth page in new popup window and you perform login in it
after login succeeded gapi gets notified and you receive your token
Why browser blocks the popup in 2nd step:
original event is not present anymore in window (window.event is destroyed).
User manually blocked the popup from current site
So if user didn't block a popup and you popup is still blocked, gapi actions look something like:
<input type="button" onclick="auth" value="click"/>
function auth() {
setTimeout(function() {
// by this time window.event is destroyed, that's why browser blocks the popup
window.open(document.URL, '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
}, 100)
}
So what you should do:
Make sure that after button click you don't perform any asynchronous actions like XHRequests or other
Make sure gapi is inited and ready by the time users click on the button, so by the time gapi needs to create a popup, window.event won't be null. So move all gapi init methods to DOMContentLoaded.
As another option you could use server-side oauth flow, which means instead of popup user will be redirected in current tab to gauth page.
I have it working now.
In the example for the picker, https://developers.google.com/picker/docs/, it calls:
<script type="text/javascript" src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
In this example, https://developers.google.com/api-client-library/javascript/start/start-js, it calls:
<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
Using client.js fixes the 'TypeError: gapi.auth is undefined' issue, and thus the login popup works.
Maybe api.js is an older version of the API?
Just Skip to the bottom
Here's the code that works for me currently. This is my first hour using this API though, so I really don't know what any of these functions do yet, nor do I know what the proper order and error handling is just yet, but at least this is functional now. Maybe it'll help someone else in the future.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Google Picker Example</title>
</head>
<body style="width: 70%; margin: 100px auto;">
<!-- Added a button to open picker -->
<button onclick="loadPicker();" >Open from GoogleDrive</button>
<div id="result"></div>
<!-- Moved to end of body tag instead of head -->
<script type="text/javascript">
// The Browser API key obtained from the Google API Console.
// Replace with your own Browser API key, or your own key.
var developerKey = '<IDK WHAT'S SUPPOSED TO GO HERE, BUT ITS OK>';
// The Client ID obtained from the Google API Console. Replace with your own Client ID.
var clientId = "<YOUR CLIENT ID GOES HERE>.apps.googleusercontent.com"
// Replace with your own project number from console.developers.google.com.
// See "Project number" under "IAM & Admin" > "Settings"
var appId = "<YOUR APP ID GOES HERE>";
// Scope to use to access user's Drive items.
var scope = ['https://www.googleapis.com/auth/drive'];
var pickerApiLoaded = false;
var oauthToken;
// Use the Google API Loader script to load the google.picker script.
function loadPicker() {
// This needs to be client:auth2 no client
gapi.load('client:auth2', {'callback': onAuthApiLoad});
gapi.load('picker', {'callback': onPickerApiLoad});
}
function onAuthApiLoad() {
// we need to init gapi.client with the clientId and scope first
gapi.client.init({
clientId: clientId,
scope: scope
});
// Now we can authorize? seems like the same thing here
window.gapi.auth.authorize(
{
'client_id': clientId,
'scope': scope,
'immediate': false
},
handleAuthResult);
}
function onPickerApiLoad() {
pickerApiLoaded = true;
createPicker();
}
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
createPicker();
}
}
// Create and render a Picker object for searching images.
function createPicker() {
// Wow this is a mess
if (pickerApiLoaded && oauthToken) {
var view = new google.picker.View(google.picker.ViewId.DOCS);
view.setMimeTypes("image/png,image/jpeg,image/jpg");
var picker = new google.picker.PickerBuilder()
.enableFeature(google.picker.Feature.NAV_HIDDEN)
.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
.setAppId(appId)
.setOAuthToken(oauthToken)
.addView(view)
.addView(new google.picker.DocsUploadView())
// Guess this is... optional?
//.setDeveloperKey(developerKey)
.setCallback(pickerCallback)
.build();
picker.setVisible(true);
}
}
// A simple callback implementation.
function pickerCallback(data) {
if (data.action == google.picker.Action.PICKED) {
var fileId = data.docs[0].id;
alert('Selected fileId: ' + fileId);
}
}
</script>
<!-- The Google API Loader script. Removed the autorun -->
<script type="text/javascript" src="https://apis.google.com/js/api.js"></script>
</body>
</html>
Edit: If you get a pop-up window that doesn't load, just close it and click the button again. That fixed another issue I just had.
Again, I don't know what I'm doing yet, so hopefully I can get a better understanding of this and clarify things later.
E2: Ah, there's more information about OAuth2 over on the Javascript GAPI documentation page which can be found here: https://developers.google.com/api-client-library/javascript/features/authentication
From another document, it appear that gapi.load('client', callback) will load auth2 if not already loaded. Calling gapi.load('client:auth2', callback) will just save 1 network request.
Note: when you authorize your application using Oauth 2.0, you do not also need to set the API key as in the first example. However, it is a good practice to do so, in case your code ever expands to handle unauthorized requests.
That explains why I could remove the API/developer key.
Edit 3: Ok the above code is technically wrong.
Warning: do not use this method alongside the recommended gapi.auth2.init and signIn flow. These are two distinct behaviors (Authorization for gapi.auth2.authorize vs Authentication for gapi.auth2.init/signIn) and will have unexpected issues if used within the same application.
autorize is for single use authentications (if you were logged into 2 google accounts for instance). While using gapi.init() is meant to be for a more long term session (like for logging in and out of a website).
How this is working currently, I do not know.
Don't use the above code, just wanted to document the progress. Here's a better demo working with getAuthResponse()
<html>
<head></head>
<body>
<div style="padding: 50px">
<h2 style="color: #2196f3;">Status: <span id='status'></span></h2>
<button id="signin-button" onclick="handleSignInClick()">Sign In</button>
<button id="signout-button" onclick="handleSignOutClick()">Sign Out</button>
<button id="signout-button" onclick="openFile()">Open File</button>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/javascript">
var cid = '<CLIENTID_HERE>';
var scope = 'https://www.googleapis.com/auth/drive';
var authenticated = false;
var pickerLoaded = false;
var auth = null;
var user = null;
var response = null;
var token = null;
var stat = $('#status');
function openFile() {
gapi.load('client:auth2', initClient);
gapi.load('picker', onPickerLoad);
}
function initClient() {
stat.html("starting");
gapi.client.init({
clientId: cid,
scope: scope
}).then(
function () {
console.log("init");
// Check if we are logged in.
auth = gapi.auth2.getAuthInstance();
auth.isSignedIn.listen(onStatusChange);
authenticated = auth.isSignedIn.get();
stat.html(authenticated);
if (authenticated) {
stat.html("Logged In!");
user = auth.currentUser.get();
response = user.getAuthResponse(true);
token = response.access_token;
showPicker();
} else {
stat.html("Logged Out!");
}
}, function(){stat.html("error")});
}
function onStatusChange(isSignedIn) {
if (isSignedIn) {
stat.html("Logged In!");
authenticated = true;
user = auth.currentUser.get();
response = user.getAuthResponse(true);
token = response.access_token;
showPicker();
showPicker();
} else {
authenticated = false;
stat.html("Logged Out!");
}
}
function handleSignInClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
function handleSignOutClick(event) {
gapi.auth2.getAuthInstance().signOut();
alert("signed out");
}
function onPickerLoad() {
pickerLoaded = true;
showPicker();
}
function showPicker() {
if (pickerLoaded && authenticated) {
var view = new google.picker.View(google.picker.ViewId.DOCS);
var picker = new google.picker.PickerBuilder();
picker.addView(view);
picker.enableFeature(google.picker.Feature.MULTISELECT_ENABLED);
picker.setOAuthToken(token);
picker.setAppId()
picker.setCallback(onDriveFileOpen);
picker = picker.build();
picker.setVisible(true);
}
}
function onDriveFileOpen(data) {
console.log(data);
if (data.action == google.picker.Action.PICKED) {
var fileId = data.docs[0].id;
console.log(fileId);
alert(data.docs[0].name);
}
}
</script>
<script async defer src="https://apis.google.com/js/api.js">
</script>
</body>
</html>

Categories

Resources