Google authentication javascript - javascript

I'm trying to do a implementation of the google login on our website. I have read the documentation and setup a application on the apis console.
I prefer that the signup dialogue is shown in a popup and after the users logins in and accepts the permissions that I would get an javascript callback. This the api also supports according to the documentation. So I build the below with help of the documentation ;-)
This first part is to load the google client script async and the init the script with the correct clientid and apikey.
$gp = new googlePlus('#Trustpilot.Web.Social.Google.ClientID', '#Trustpilot.Web.Social.Google.ApiKey');
(function () {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/client.js?onload=googlePlusClientLoad';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
The next part it the part that uses the google client api. handleClientLoad() is called when the client.js is loaded. The method checks if the use is authenticated. If the user is, the idea is that I want to login the user.
If the user is not already authenticated there will be a button and when clicking handleAuthClick() is called, it basic does the same as handleClientLoad() but there will be an popup where the user most login (with google account) and accept permissions. After login handleAuthResult() is called which logins the user.
function googlePlus(clientId, apiKey) {
this.clientId = clientId;
this.apiKey = apiKey;
this.scopes = 'https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.email';
/// This method is called when the javascript is loaded async
this.handleClientLoad = function() {
gapi.client.setApiKey(this.apiKey);
window.setTimeout(this.authorize(true), 1);
};
this.handleAuthResult = function (authResult) {
console.log(authResult);
if (authResult && !authResult.error) {
var token = gapi.auth.getToken();
console.log(token);
}
else if (authResult && authResult.error) {
alert(authResult.error);
}
};
this.handleAuthClick = function(event) {
this.authorize(false);
return false;
};
this.makeApiCall = function() {
gapi.client.load('plus', 'v1', function () {
var request = gapi.client.plus.people.get({
'userId': 'me'
});
request.execute(function (resp) {
console.log(resp);
});
});
};
this.authorize = function (immediate) {
gapi.auth.authorize({ client_id: this.clientId, scope: this.scopes, immediate: immediate }, this.handleAuthResult());
//gapi.auth.authorize({ client_id: this.clientId, scope: this.scopes, immediate: immediate }, this.handleAuthResult());
};
}
var googlePlusClientLoad = function() {
$gp.handleClientLoad();
};
So now to the problem:
When handleClientLoad is called the user is never authenticated even
if the user already has autehnticated.
If the user uses handleAuthClick() the popup shows and login, permissions and
callback to handleAuthResult() work. But the parameter authResult is
always nothing (should be something accouring to the documentation).
If I try multiple times without reloading the page I can sometimes
get the makeApiCall() and gapi.auth.getToken() to work and get the information I need.

There are two issues in your code :
The API key is not required, you can remove it. You get the user token through OAuth2 and that is enough.
In authorize(), the handleAuthResult method is not correctly called, remove the parenthesis at the end of the function name. You do not want to execute the function, just pass its reference. Here's what the authorize method must look like :
gapi.auth.authorize({ client_id: this.clientId, scope: this.scopes, immediate: immediate }, this.handleAuthResult);
Note the difference in parenthesis.

Related

Problems with asynchronously loading Google Map via Javascript

I'm trying to asynchonously a Google Map. There are a lot of examples out there, but the ones a find use global(?) functions to showcase the concept of using callback function. The project I'm working on, however, defines various objects and uses prototypes to add properties/functions to them. The relevant source code looks as follows:
var Demo = new Array();
Demo.Content = function() { };
Demo.Content.prototype = { };
Demo.Content.Controller = function() {
this.contentView = new Demo.Content.View(this);
};
Demo.Content.Controller.prototype = {
initialize : function() {
this.contentView.initialize();
},
onGoogleMapsScriptLoaded : function() {
alert('onGoogleMapsScriptLoaded');
},
};
Demo.Content.View = function(controller) {
this.controller = controller;
};
Demo.Content.View.prototype = {
initialize : function() {
// now called from background script (Chrome extensions)
//this.asyncLoadGoogleMap();
},
asyncLoadGoogleMap : function() {
$.getScript("http://maps.googleapis.com/maps/api/js?v=3&sensor=false&callback=???")
.done(function (script, textStatus) {
alert("Google map script loaded successfully");
})
.fail(function (jqxhr, settings, ex) {
alert("Could not load Google Map script: " + ex);
});
},
};
contentController = new Demo.Content.Controller();
contentController.initialize();
While I get the success message "Google map script loaded successfully", I have no idea what to use as callback function -- always something is undefined. I tried, for example, contentController.test -- Cannot read property 'onGoogleMapsScriptLoaded' of undefined -- or indeed using a global function as in the examples on the Web. How do I set the callback function? Or do I have a more fundamental mistake here?
EDIT: The whole things is part of a content script for a Google Chrome extensions -- in case this important. This includes that I now load the map when the page is really finished loading using
chrome.tabs.onUpdated.addListener( function (tabId, changeInfo, tab) {
if (changeInfo.status == 'complete') {
chrome.tabs.sendMessage(tabId, {action: 'load-map'}, function(){});
}
});
in the background script. The content script has a message listener that invokes asyncLoadGoogleMap. So the page should be completely there. Still, I get the same errors.
Here is the code for AngularJS, but it still creates the global callback function:
// Google async initializer needs global function, so we use $window
angular.module('GoogleMapsInitializer')
.factory('Initializer', function($window, $q){
// maps loader deferred object
var mapsDefer = $q.defer();
// Google's url for async maps initialization accepting callback function
var asyncUrl = 'https://maps.googleapis.com/maps/api/js?callback=';
// async loader
var asyncLoad = function(asyncUrl, callbackName) {
var script = document.createElement('script');
//script.type = 'text/javascript';
script.src = asyncUrl + callbackName;
document.body.appendChild(script);
};
// callback function - resolving promise after maps successfully loaded
$window.googleMapsInitialized = function () {
mapsDefer.resolve();
};
// loading google maps
asyncLoad(asyncUrl, 'googleMapsInitialized');
return {
// usage: Initializer.mapsInitialized.then(callback)
mapsInitialized : mapsDefer.promise
};
})

Google Auth User Email Not Present

I'm using the javascript api for Google Auth 2.0 . I'm running into the problem where the users email is not showing up, even though I request with https://www.googleapis.com/auth/userinfo.email .
My code looks like this:
gapi.auth.authorize({
client_id : 'xxxxxxxxxx.apps.googleusercontent.com',
scope : ['https://www.googleapis.com/auth/plus.me', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile'],
immediate : false
}, function(result) {
if (result != null) {
gapi.client.load('plus', 'v1', function() {
var request = gapi.client.plus.people.get({
'userId' : 'me'
});
request.execute(function(resp) {
console.log(resp);
});
});
}
});
What am I missing to get the user's email?
While the userinfo.email role gives you access to the information, the plus v1 client doesn't provide it. You will need to make an additional call to a different endpoint to get the info.
You will need the oauth2 v2 endpoint, which you can request with gapi.client.load('oauth2', 'v2', callback). The endpoint itself that you want is gapi.client.oauth2.userinfo.get(). This is untested, but the code might look something like:
gapi.client.load('oath2','v2',function(){
gapi.client.oauth2.userinfo.get().execute(function(resp){
console.log(resp);
});
});
See How can I get the user's email address in google apps? and Why can't I retrieve the users email from Google Plus API after getting permission for some related questions and https://developers.google.com/accounts/docs/OAuth2 for more details from the official doc.
Here's how I did it:
function tryAuth() {
var clientId = CLIENT_ID;
var configString = {
client_id: clientId,
scope: SCOPE,
immediate: 'false'
};
gapi.auth.authorize(configString, handleAuthResult);
}
Where SCOPE = 'https://www.googleapis.com/auth/fusiontables email';
Replace https://www.googleapis.com/auth/fusiontables scope with your scope but keep ' email' .
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
var access_token = authResult.access_token;
alert('Successfully logged in.' + access_token);
tryGetEmail(access_token);
}
And then
function tryGetEmail(access_token) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", 'https://www.googleapis.com/oauth2/v1/userinfo?access_token=' + access_token, false );
xmlHttp.send( null );
if(xmlHttp.status == 200) {
var strJSON = xmlHttp.responseText;
var objJSON = eval("(function(){return " + strJSON + ";})()");
email = objJSON.email;
alert('got email ' + email);
}
}
The userinfo endpoint and oauth2 v2 are being deprecated. The older answers are for the old system. All the details for migration are here:
https://developers.google.com/+/api/auth-migration#email
In short: put 'email' instead of 'h ttps://www.googleapis.com/auth/userinfo.email' for your scope, and the G+ email will be included as the first entry of the 'emails' property in the 'person' object you're fetching. There's also apparently an option described in the link to pull it out of the ID token, referenced in the link above.
Full example

Google api example not working

I'm trying to run the following Google Api example:
<:html:>
<:body:>
<:div id='content':>
<:h1:>Events<:/h1:>
<:ul id='events':><:/ul:>
<:/div:>
<:a href='#' id='authorize-button' onclick='handleAuthClick();':>Login<:/a:>
<:script:>
var clientId = redacted;
var apiKey = redacted;
var scopes = 'https://www.googleapis.com/auth/calendar';
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
checkAuth();
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true},
handleAuthResult);
}
function handleAuthResult(authResult) {
var authorizeButton = document.getElementById('authorize-button');
if (authResult) {
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},
handleAuthResult);
return false;
}
function makeApiCall() {
gapi.client.load('calendar', 'v3', function() {
var request = gapi.client.calendar.events.list({
'calendarId': 'primary'
});
request.execute(function(resp) {
for (var i = 0; i <: resp.items.length; i++) {
var li = document.createElement('li');
li.appendChild(document.createTextNode(resp.items[i].summary));
document.getElementById('events').appendChild(li);
}
});
});
}
<:/script:>
<:script src="https://apis.google.com/js/client.js?onload=handleClientLoad":><:/script:>
<:/body:>
<:/html:>
Whenever I run the file I get two errors:
gapi.client object is null or undefined
window.sessionStorage.length object is null or undefined
For the last error it gives this url as the source:
https://apis.google.com//scs/apps-static//js/k=oz.gapi.nl.4xKjGS6fluU.O/m=client/am=QQ/rt=j/d=1/rs=AItRSTMdnq2AHV2okN-h3tZllkPQibG86w/cb=gapi.loaded_0
I'm running IE8, does anyone have an idea what's wrong?
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
checkAuth();
}
You're calling checkAuth() twice. Maybe the setTimeout() is necessary for a valid response?
I have noticed that the same example (like hello world) from official documentation page here
is declaring to use variables:
// The Browser API key obtained from the Google Developers Console.
var developerKey = 'xxxxxxxYYYYYYYY-12345678';
// The Client ID obtained from the Google Developers Console. Replace with your own Client ID.
var clientId = "1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com"
// Scope to use to access user's photos.
var scope = ['https://www.googleapis.com/auth/photos'];
but developerKey variable if browser API key is used returns error that API is inaccessible and when creating server key and using it instead everything works fine.

Wrap angular $resource requests not returning POST data

I'm working on wrapping my $resource requests in a simple wrapper. The main idea
is to be able to add some logic before the request is made. I've followed the nice article written by Nils.
Here you can see a service definition to access the REST API module.
resources.factory('Device', ['RequestWrapper', '$resource', 'lelylan.config', function(RequestWrapper, $http, config) {
var resource = $resource(config.endpoint + '/devices/:id', { id: '#id' });
return RequestWrapper.wrap(resource, ['get', 'query', 'save', 'delete']);
}]);
And here you can see the request wrapper definition.
resources.factory('RequestWrapper', ['AccessToken', function(AccessToken) {
var requestWrapper = {};
var token;
requestWrapper.wrap = function(resource, actions) {
token = AccessToken.initialize();
var wrappedResource = resource;
for (var i=0; i < actions.length; i++) { request(wrappedResource, actions[i]); };
return wrappedResource;
};
var request = function(resource, action) {
resource['_' + action] = resource[action];
resource[action] = function(param, data, success, error) {
(AccessToken.get().access_token) ? setAuthorizationHeader() : deleteAuthorizationHeader()
return resource['_' + action](param, data, success, error);
};
};
var setAuthorizationHeader = function() {
$http.defaults.headers.common['Authorization'] = 'Bearer ' + token.access_token;
};
var deleteAuthorizationHeader = function() {
delete $http.defaults.headers.common['Authorization']
};
return requestWrapper;
}]);
Everything works just fine for the GET and DELETE methods (the ones that does not returns
a body seems), but I can't get $save working. What happens is that when the JSON of the
created resources returns it is not added. I have only the data I've set on the creation
phase. Let me make an example.
In this case we use the wrapped resource. If I try to get the #updated_at attribute I can't
see it. In the Chrome inspector I can see how the resource is successfully created.
$scope.device = new Device({ name: 'Angular light', type: 'http://localhost:9000/types/50bf5af4d033a95486000002' });
$scope.device.$save(function(){ console.log('Device Wrapped', $scope.device.created_at) });
# => undefined
If I use $resource everything works fine.
// Suppose authorization is already set
var Resource = $resource('http://localhost\\:9000/devices/:id');
$scope.resource = new Resource({ name: 'Angular light', type: 'http://localhost:9000/types/50bf5af4d033a95486000002' });
$scope.resource.$save(function(){ console.log('Device Base', $scope.resource.created_at); });
# => 2013-02-09T12:26:01Z
I started to check the angular-resource.js code but after few hours I couldn't really figure
it out. I can't get why the body is returned, but in the wrapper resource it is not accessible.
Any idea or help would be appreciated. Thanks.
While diving into AngularJS source code I've found the solution.
The problem was that the wrapper was returning a function instead of an object and this was giving some problems. The solution is to change the following row in the Wrapper:
return resource['_' + action](param, data, success, error);
with this one:
return resource['_' + action].call(this, params, data, success, error);
Why? The fast answer is because in the source code of angular-resource they use it. Actually #call run the function sending this to the calling object. It is often used to initialize an object. Learn more here.

Javascript can't set variable value

I'm trying to get this function to set sortedScores to a value... whenever i do console.log anywhere it seems to store the values right, but it can't actually set sortedScores properly...
function facebook(sortedfriends) {
var myID;
var access_token;
FB.init({
appId : something,
status : true,
cookie : true,
xfbml : true,
});
FB.getLoginStatus(function(response, sortedfriends) {
if(!response.session) {
FB.login(function(response) {
myId = response.session.uid;
access_token = response.session.access_token;
//does stuff
if (!response.session) {
console.log('User cancelled login or did not fully authorize.');
}
});
}
else if(response.session) {
myId = response.session.uid;
access_token = response.session.access_token;
var D = new Array();
this.access_token = access_token;
FB.api('/me/feed?access_token=' + access_token + '&limit=100', function(response, sortedfriends) {
for( i=0; i<response.data.length; i++) {
var msg = response.data[i];
D.push(msg);
}
sortedfriends = somefunction(D, myID);
//i know somefunction works because if i do console.log(sortedfriends) it shows me the right values...
});
}
});
}
when i try
var friends;
facebook(friends);
friends is just undefined... halp?
It is not clear where you are trying to access sortedfriends, but I'm guessing that your issue is that the Facebook login is asynchronous, so the response comes some time later after the facebook function has completed. Thus, if you're trying to use the sortedfriends data right after you call the facebook function, the data will not be there yet because the login call has not completed yet.
The only way to use the sortedfriends data is from the success handler to the login call (where your console.log already verifies that the data is there). If you want to use it in other code, then call that other code from the success handler to the login call. You cannot call that code right after calling the facebook function.
This logic will not work because of the asynchronous nature of the login call (it hasn't completed when the facebook call returns and execution continues after it:
var sortedfriends = [];
facebook(sortedfriends);
// use sortedfriends here
Instead, you have to do this type of structure:
var sortedfriends = [];
facebook(sortedfriends);
// nothing executes here
And, inside your facebook call where you have this, add a function call to whatever code you want to use the sortedfriends data:
else if(response.session) {
myId = response.session.uid;
access_token = response.session.access_token;
var D = new Array();
this.access_token = access_token;
FB.api('/me/feed?access_token=' + access_token + '&limit=100', function(response, sortedfriends) {
for( i=0; i<response.data.length; i++) {
var msg = response.data[i];
D.push(msg);
}
sortedfriends = somefunction(D, myID);
//i know somefunction works because if i do console.log(sortedfriends) it shows me the right values...
// call the rest of your code here that will use sortedfriends
doSomethingWithSortedFriends(sortedfriends);
});
javascript functions do not change incoming references. You cannot pass undefined and expect it will become defined after the function executes.
Instead, you should make your sortedFriends a global variable and not pass it as function parameter. Please note, that the value of sortedFriends will be set after the ajax query inside getLoginStatus is executed, so you will not be able to use it immediately after you execute your facebook function.

Categories

Resources