Google Calendar Api Intermittent 401 - javascript

I have a simple code that Logs into to Google to use the Google Calendar API and creates and event for the user. The code works fine but every so often I get a 401 - Login Required error from Google. If I open the developer console in the browser and try again it works... which is very odd
var scopes = 'https://www.googleapis.com/auth/calendar';
var clientId = 'CLIENT_ID';
var apiKey = 'API_KEY';
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
}
function handleAuthResult(authResult) {
var authorizeButton = $('#calendarAuth');
var calendarFrame = $('#calendarFrame');
if (authResult && !authResult.error) {
authorizeButton.remove();
calendarFrame.show();
calendarFrame.attr('src', calendarFrame.attr('src')); //Hack to reload the iframe
} else {
authorizeButton.show();
calendarFrame.hide();
}
}
function Authorize() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false}, handleAuthResult);
return false;
}
function GoogleScheduleFollowup(followup) {
gapi.client.load('calendar', 'v3', function () {
var request = gapi.client.calendar.events.insert({
"calendarId": "primary",
resource: {
"summary": followup.title,
"start": {
"dateTime": window.GetFullDateString(followup.date, followup.time)
},
"end": {
"dateTime": window.GetFullDateString(followup.date, followup.time)
}
}
});
request.execute(function (resp) {
console.log(resp);
});
}
Everything seems to be well configured in the API Console, and I'm sure we haven't reached the quota (either total or requests per second). As an example today I have made 133 requests of which 36 failed with this error.
I have tried to call gapi.auth.authorize every 10 minutes to see if the problem was a session timeout, and as I read in another question I tried removing this line gapi.client.setApiKey(apiKey);, both without success

Related

Trying to implement ADAL (Azure AD) in Javascript, keep getting a login/redirect loop

So I've got to create a calendar in html that gets events from Outlook and then deploy that as a custom page to Sharepoint, so it can be included as a webpart/iframe in site collections.
The problem is that I've tried adding ADAL security because you need to be logged in & send a token to Microsoft Exchange Online API in order to get calendar events etc. To display the calendar part, I'm using FullCalendar.io .
Now I've been keep getting a login/redirect loop that never ends. Does anyone see the fault in code? Here it is:
var $this = this;
$(document).ready(function() {
debugger;
window.config = {
tenantId: {tenant},
clientId: {clientid},
popUp: true,
callback: callbackFunction,
redirectUri: {custom aspx page URL on our Sharepoint},
cacheLocation: 'localStorage'
};
var authenticationContext = new AuthenticationContext(config);
authenticationContext.handleWindowCallback();
function callbackFunction(errorDesc, token, error, tokenType) {
alert('callbackFunction reached!');
}
var items = null;
if (authenticationContext.TokenCache) {
items = authenticationContext.TokenCache.ReadItems();
}
if (authenticationContext['_user']) {
authenticationContext.acquireToken(config.clientId, function (errorDesc, token, error) {
if (error) { //acquire token failure
if (config.popUp) {
// If using popup flows
authenticationContext.acquireTokenPopup(config.clientId, null, null, function (errorDesc, token, error)
{});
}
else {
// In this case the callback passed in the Authentication request constructor will be called.
authenticationContext.acquireTokenRedirect(config.clientId, null, null);
}
}
else {
//acquired token successfully
// alert('token success');
$this.DisplayEvents(token);
}
});
}
else {
// Initiate login
authenticationContext.login();
}
});
function DisplayEvents(adalToken) {
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay,listWeek'
},
navLinks: true, // can click day/week names to navigate views
editable: true,
eventLimit: true, // allow "more" link when too many events
events: function(start, end, timezone, callback) {
var headers = new Headers();
var bearerToken = "Bearer " + adalToken;
headers.append('Authorization', bearer);
var options = {
method: 'GET',
headers: headers
};
var exchangeEndpoint = 'https://outlook.office.com/api/v2.0/me/events';
fetch(exchangeEndpoint, options).then(function (response) {
alert('Response data from successful call: ' + response);
});
}
});
}
So the code does get to "acquire token" and then the last "else", so "$this.DisplayEvents(token)" does get called! However, after acquire token, the app just keeps redirecting forever and ever... The Reply URL in my Azure AD App registration is also the window.config redirectURL value, or else I'd get an error stating the reply URL's don't match between request and Azure.
Does anyone know where it's going wrong?
I can reproduce your issue on my side by using your code. If you use authContext.getCachedUser() to check login status, redirect issue will disappear.
if (authContext.getCachedUser()) {
authContext.acquireToken(config.clientId, function (error, token) {
if (error) { //acquire token failure
if (config.popUp) {
// If using popup flows
authContext.acquireTokenPopup(config.clientId, null, null, function (errorDesc, token, error) { });
}
else {
// In this case the callback passed in the Authentication request constructor will be called.
authContext.acquireTokenRedirect(config.clientId, null, null);
}
}
else {
//acquired token successfully
// alert('token success');
alert(token);
}
});
}
else {
// Initiate login
authContext.login();
}

401 error when my javascript app tries to access my google cloud endpoints

The project is on Google Appengine cloud endpoints framework.
Python in the backend.
I'm also using endpoints_proto_datastore (not sure if that makes a difference)
Here is my html file -
<html>
<body>
<script>
clientId = 'myclientid-something-something-something.apps.googleusercontent.com'
loginScope = 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/admin.directory.user.readonly https://www.googleapis.com/auth/admin.directory.customer.readonly https://www.googleapis.com/auth/gmail.settings.basic';
apiKey = 'my-api-key-from-cloud-console';
doSignIn = function() {
console.log("calling doSignIn");
gapi.client.init({apiKey: apiKey, clientId: clientId,scope: loginScope}).then(renderSignIn);
}
renderSignIn = function(){
gapi.signin2.render('my-signin', {
'scope': loginScope,
'width': 'inherit',
'height': 50,
'longtitle': true,
'theme': 'light',
'onsuccess': getOfflineAccess,
'onfailure': function(){console.log("error")}
});
};
getOfflineAccess =function(){
console.log("calling getOfflineAccess");
gapi.auth2.getAuthInstance().grantOfflineAccess({'redirect_uri': 'postmessage'}).then(getApiAuthorization);
}
getApiAuthorization = function(){
console.log("calling getApiAuthorization");
gapi.auth.authorize({client_id: clientId,scope: loginScope, immediate: false},singedInCallback);
};
singedInCallback = function(authResponse) {
console.log("calling signInCallback");
gapi.client.endpointsapp.userOfflineAccessCode.insert({'auth_code':authResponse.code})
.then( function(){console.log("success");},
function(){console.log("error");}
);
};
init = function() {
console.log("calling init");
var apisToLoad;
var callback = function() {
if (--apisToLoad == 0) {
doSignIn();
}
}
apisToLoad = 2;
gapi.client.load('endpointsapp','v1',callback,"https://endpointsapp.appspot.com/_ah/api"); //dummy name for app
gapi.load('client:auth2', callback);
};
</script>
<div id="my-signin"></div>
<script src="https://apis.google.com/js/api.js?onload=init"></script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>
<script src="https://apis.google.com/js/platform.js"></script>
</body>
</html>
Everything goes smooth at first.
I get a google signing button.
I click on it and then all required permissions are granted.
When the actual API hit is made. That gives me a 401.
The response that I get from the API (gapi.client.endpointsapp.userOfflineAccessCode.insert) is :
{
"error": {
"code": 401,
"errors": [
{
"domain": "global",
"message": "Invalid token.",
"reason": "required"
}
],
"message": "Invalid token."
}
}
When I try the same api endpoint using the google api explorer, if I'm authenticated, everything works, without any issue.
I've been trying to debug this for an entire day but just can't figure out what I'm doing wrong.
Any help is highly appreciated.
ok found the issue. Very basic mistake.
According to https://cloud.google.com/endpoints/docs/frameworks/python/create_api allowed_client_ids is a required field if the API uses authentication. I was not providing this parameter and expecting the API to be available to all client_ids by default.

Properly retrieve username and useful values (site title, copyright, etc.)

I have a simple web app based on this project ( https://github.com/arthurkao/angular-drywall ), running with NodeJS and AngularJS as the front-end.
I'm trying to set up a simple page that displays a list of all connected users on a map (using Google Maps, Geolocation and PubNub).
Here's how I'm actually doing it:
angular.module('base').controller('TravelCtrl',
function($rootScope, $scope, NgMap, security, $geolocation, PubNub){
$rootScope.extusers = []; //remote users
$scope.initTravel = function() { //declare the init function
PubNub.init({
subscribe_key: $rootScope.security.keys.psk,
publish_key: $rootScope.security.keys.ppk,
uuid: $rootScope.security.currentUser.username,
ssl: true
});
PubNub.ngSubscribe({
channel: "travel",
state: {
position: {},
}
});
console.log("Loaded Travel");
$geolocation.getCurrentPosition({
timeout: 60000
}).then(function(position) { //when location is retreived
$scope.position = position;
PubNub.ngSubscribe({
channel: "travel",
state: {
position: {
lat: Math.floor($scope.position.coords.latitude*1000)/1000, //decrease accuracy
long: Math.floor($scope.position.coords.longitude*1000)/1000,
},
}
});
$rootScope.$on(PubNub.ngPrsEv("travel"), function(event, payload) {
$scope.$apply(function() {
$scope.extusers = PubNub.ngPresenceData("travel");
});
});
PubNub.ngHereNow({ channel: "travel" });
$scope.showInfo = function(evt, marker) { //show user window on map
$scope.extuser = marker;
$scope.showInfoWindow('infoWindow');
};
});
};
if ($rootScope.hasLoaded()) { //if username and keys are already loaded, then init module
$scope.initTravel();
} else { //else, wait for username and keys to be loaded
$rootScope.$on('info-loaded', function(event, args) {
$scope.initTravel();
});
}
}
);
Although it works, it seems like it's very buggy and only loads sometimes. Occasionally, I get this:
Result screenshot
I really don't know what I'm doing wrong, as I simply followed the tutorials on PubNub's AngularJS SDK.
I think this has to do with how I'm initialising the application.
angular.module('app').run(['$location', '$rootScope', 'security', function($location, $rootScope, security) {
// Get the current user when the application starts
// (in case they are still logged in from a previous session)
$rootScope.hasLoaded = function() {
return (security.keys && security.info && security.currentUser); //check if everything is loaded correctly
};
$rootScope.checkLoading = function() {
if ($rootScope.hasLoaded()) {
$rootScope.$broadcast('info-loaded'); //broadcast event to "TravelCtrl" in order to init the module
}
};
security.requestKeys().then($rootScope.checkLoading); //request secret keys
security.requestSiteInfo().then($rootScope.checkLoading); //then templating info (site title, copyright, etc.)
security.requestCurrentUser().then($rootScope.checkLoading); //and finally, current user (name, id, etc.)
$rootScope.security = security;
// add a listener to $routeChangeSuccess
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
$rootScope.title = current.$$route && current.$$route.title? current.$$route.title: 'Default title';
});
}]);
1- Request secret keys, site info and current user with JSON API.
2- Wait until everything's loaded then init the application with the appropriate keys (PubNub, Google Maps)
--
My question is:
How do you instantiate an AngularJS app after retrieving useful information via a RESTful API?
I'm pretty new to AngularJS, and I wouldn't be surprised if my approach is totally ridiculous, but I really need to get some advice on this.
Thanks in advance for your help,
Ulysse
You don't have to wait that the AJAX Query ended to initate the angular APPs.
you can use the $http promise ( details her )
In the controller :
// Simple GET request example:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
// data is now accessible in the html
$scope.data = response ;
// you can call a function to add markers on your maps with the received data
addMarkerOnMap(response);
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
You can also add a watch on some variable to wait modification on them :
// you should have $scope.yourVarName declared.
$scope.$watch('yourVarName', function(newValue, oldValue) {
console.log(newValue);
});
Or watch a list/object
$scope.$watchCollection('[var1,var2]', function () {
},true);

Make google auth request gapi.auth without a popup

Need to make auth request in js but the browser does not support popups. Is there any way to redirect to a new url or show the request in the in html5 page of the application
By using this code check if user authorized your app
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, callbackAuthResult);
Note: immediate:true
if you set immediate true then it wont show popup.
You see? You don't open the popup, and manage the stuff in the callback. This callback is usually used for post-processes. Here we use it for authenticating.
in callbackAuthResult:
callbackAuthResult = function (authResult) {
var authorizeButton = document.getElementById('authorize-button');
if (authResult && !authResult.error) {
authorizeButton.style.display = 'none';
// do your processing here
} else {
authorizeButton.style.display = 'block';
authorizeButton.onclick = callbackAuthClick;
}
}
callbackAuthClick = function (event) {
gapi.auth.authorize({
client_id: clientId,
scope: scopes,
immediate: false
}, handleAuthResult);
return false;
}

Google Analytics API Javascript: 403 Forbidden Error

For the love of Bob, someone please help me out...
I'm trying to use the Google Analytics API (Javascript Library) to get some Analytics info. I've registered the my app and set up the oauth2 stuff. I can return the access token jsut fine, but when i try to send a request to actually grab Analytics info, it returns a 403 forbidden error. Here's my code:
function auth() {
var config = {
'client_id': '[my_client_id]',
'scope': 'https://www.googleapis.com/auth/analytics.readonly'
};
gapi.auth.authorize(config, function() {
var retObj = gapi.auth.getToken();
makeRequest(retObj.access_token);
});
}
function makeRequest(accessToken) {
var restRequest = gapi.client.request({
'path': '/analytics/v3/data/ga',
'params': {
'access_token': accessToken,
'ids': 'ga:[table_number]',
'metrics': 'ga:pageviews,ga:uniquePageviews',
'start-date': '2011-11-01',
'end-date' : '2011-12-01'
}
});
restRequest.execute(function(resp) { console.log(resp); });
}
The auth() function is executed via a button click and like I said, getting the access token is not the issue. It's when I execute the makeRequest function that I get the 403 error. Anyone have any clue as to what the deal is here?
Thanks to anyone who answers in advance!!
In my case I was getting 403 Forbidden because in my browser I was logged into Google with an account that didn't have permission to the GA profile I was trying to access. Before discovering that issue, I was having trouble with the tableID for which Aksival posted the solution above.
Here's my working code for your reference:
<script type="text/javascript">
//GET THESE HERE https://code.google.com/apis/console/
var clientId = 'YOURCLIENTIDHERE';
var apiKey = 'YOURAPIKEYHERE';
//GET THIS HERE http://code.google.com/apis/analytics/docs/gdata/v3/gdataAuthorization.html
var scopes = 'https://www.googleapis.com/auth/analytics.readonly';
//INITIALIZE
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
}
function handleAuthResult(authResult) {
if (authResult) {
makeApiCall();
} else {
requestAuth();
}
}
function requestAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false}, handleAuthResult);
}
function makeApiCall() {
gapi.client.load('analytics', 'v3', function() {
var request = gapi.client.analytics.data.ga.get({
'ids':'ga:YOURTABLEIDHERE', 'start-date':'2012-01-01', 'end-date':'2012-02-01', 'metrics':'ga:visits', 'metrics':'ga:visits', 'start-index':1, 'max-results':1000
});
request.execute(function(resp) { console.log(resp.totalsForAllResults); });
});
}
</script>
<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
I had the same issue. Turns out I was passing in the wrong [table_number].
You need to query
accounts/[account-id]/webproperties/[webproperties-id]/profiles
and use the 'id' field of the appropriate property. (I was using the internalWebPropertyId from the webproperties query at first, which is why it was failing.)
Works like a charm now.

Categories