Angular HTTP within a HTTP Interceptor - javascript

I need to append the necessary HMAC headers to a request. This should not be very difficult however I am starting to get frustrated. What is wrong with the following code. The actual http call I am doing works; I have run this call myself and it returns the necessary data. It does not work inside the interceptor.
I merely want to get the current implementation working before I add whitelist or blacklist and other customizable data for this interceptor. This is not a question about hmac however but with promises.
The error in this interceptor is with the entire promise line starting at $http(...). When i remove this block and use it as is (minus promise execution) it works fine. As soon as i uncomment the line it gets stuck in a loop and crashes chrome. Everywhere I have read says this is how it is done, but this clearly does not work.
function requestInterceptor(config){
var $http = $injector.get('$http');
var deferred = $q.defer();
$http.get(hmacApiEndpoint, {cache: true}).then(function(data){
console.log('HMAC - Success', data)
deferred.resolve(config)
}, function(config){
console.log('HMAC - Error', config)
deferred.resolve(config)
})
return deferred.promise;
}
return {
request: requestInterceptor
};
Does this have something to do with the fact that angulars $http promise is a different implementation than that of '$q'?

It doesn't look like you are actually amending the config with the newly obtainted HMAC.
Also, you'd need to protect against your requestInterceptor intercepting the call to obtain the HMAC, thus resulting in an infinite loop.
And lastly, you don't need deferred here - just return the promise produced by $http (or $http.then()):
function requestInterceptor(config){
var $http = $injector.get('$http');
// just return, if this is a call to get HMAC
if (config.url === hmacApiEndpoint) return config;
return $http.get(hmacApiEndpoint, {cache: true})
.then(function(response){
console.log('HMAC - Success', response.data)
// not sure where the HMAC needs to go
config.headers.Authorization = response.data;
return config;
})
.catch(function(){
return $q.reject("failed to obtain HMAC");
});
}
return {
request: requestInterceptor
};

Related

AngularJs: how to hold all the $http request if its thrown unauthorised request error refresh token and resume all the request

i have developed single page application in angularjs. i have implemented the refresh token mechanism. refresh token suppose to refresh every 30 minutes. I am trying to handle refresh token in responseError of interceptor. I m trying to hold request if it returns 401 unauthorised error. Is there any mechanism to hold all the request once it return 401 error then refresh token and resume all request with new token.
Is it right way to handle the refresh token, here is sample code
$provide.factory('httpTokenInterceptor', function ($q, $injector, $cookies) {
return {
// On request sending
request: function (config) {
config.headers = config.headers || {};
// get this data from $cookies
var globals = $cookies.getObject('globals') || {};
//console.log(globals);
if (globals.authData)
config.headers.Authorization = 'Bearer ' + globals.authData.access_token;
return config;
},
// On response failure
responseError: function (rejection) {
console.log('AuthTokenHttpInterceptor responseError');
console.log(rejection);
if (rejection.status === 401) {
//hold current and all pending request
var aService = $injector.get('authenticationService');
aService.getRefreshToken().then(function(response) {
//need to resume all the request here
deferred.resolve(response);
});
return deferred.promise;
}
return $q.reject(rejection);
}
};
});
In short, you don't want to hold up any of your HTTP calls like that.
Your solution will go and refresh your token after one of your HTTP calls already failed. Also, just to be clear, your code is adding Authorization header even on HTTP calls that are getting resources like HTML templates. If you don't want to do this, then you should restrict that as well.
For one solution, check out this link. It doesn't use any particular library for handling JWT tokens, but you will have to create a wrapper around this implementation to use it wherever you need to do a HTTP call.
My suggestion (and personal preference when handling JWT tokens) is using the angular-jwt library. It's really easy to set up and you can check it out here.
There more complex libraries like auth0, which can do a lot of other stuff, and can be used in conjuction with angular-jwt library. Check out this link to see how to handle token refreshing both prior to a HTTP call and on page refresh.
Hope this helps.
You can hold requests and resume them using AngularJS Interceptors.
authInterceptor.$inject = ['$q', '$rootScope'];
function authInterceptor($q, $rootScope) {
return {
request: function(config) {
var deferred = $q.defer();
$rootScope.$watch('continue', function(value) {
if(value === true)
deferred.resolve(config);
});
return deferred.promise;
}
};
}
In the above example all of the requests hold until $rootScope.continue becomes true. Otherwise they will wait forever.

AngularJS - trouble with $http's success() and error() not being called properly

I am working on this code and I have a really weird issue. I am using AngularJS $http request, and trying to run a success or error afterwards. I am currently getting a 404 from the server (the client says) but the server isn't barfing up a 404 (in fact it says it sent a 200 response).
This $http post doesn't hit either the 'success' or 'error' handlers and I don't know what to do for debugging it.
All other functions using RequestService work perfectly.
var refreshBusinesses = function() {
console.log("refreshBusinesses");
RequestService.post('user/managed_businesses', {}).then(function(businesses){
console.log('got busineses ', businesses)
$scope.businesses = businesses
}, function(error){
console.log("ERROR!");
$scope.error = error.message;
console.log('business refresh error ', error)
})
}
Most probably your service RequestService does not handle promise properly. I believe it is a wrapper around angular's $http. If so, probably it does not reject promise on error, but simply returns some value.
So to solve it check your RequestService that it handles error situation properly:
$http.post('url', data).then(
function(res) {
return res;
},
function(error) {
var deferred = $q.defer();
return deferred.reject(error);
});
$q docs can be found here https://docs.angularjs.org/api/ng/service/$q

How should AngularJS handle 403 error in $http.post due to outdated XSRF token?

An AngularJS version 1.4.8 app is getting an unhandled 403 error when its login form sends data to a backend REST authentication service after the user's browser has been left open for many (16 in this case) hours. Upon deeper analysis, the root cause is that the client AngularJS app has outdated cookies for XSRF-TOKEN and JSESSIONID, which causes the backend Spring Security to reject the request to the public /login1 service because Spring thinks the request is cross site request forgery.
The problem can be resolved manually if the user closes all browser windows and then re-opens a new browser window before making the request again. But this is not an acceptable user experience. I have read the AngularJS documentation at this link, and I see that I can add an errorCallback function, but how specifically should i re-write the function to handle the 403 error?
Here is the original this.logForm() method in the authorization service, which you can see does not handle 403 errors:
this.logForm = function(isValid) {
if (isValid) {
var usercredentials = {type:"resultmessage", name: this.credentials.username, encpwd: this.credentials.password };
$http.post('/login1', usercredentials)
.then(
function(response, $cookies) {
if(response.data.content=='login1success'){// do some stuff
} else {// do other stuff
}
}
);
}
};
Here is my very rough attempt at a revised version of the this.logForm() method attempting to handle a 403 error following the example in the AngularJS documentation:
this.logForm = function(isValid) {
if (isValid) {
var usercredentials = {type:"resultmessage", name: this.credentials.username, encpwd: this.credentials.password };
$http({ method: 'POST', url: '/login1', usercredentials })
.then(
function successCallback(response, $cookies) {
// this callback will be called asynchronously when the response is available
if(response.data.content=='login1success'){// do some stuff
} else {// do other stuff
}
},
function errorCallback(response, status) {// is status a valid parameter to place here to get the error code?
// called asynchronously if an error occurs or server returns response with an error status.
if(status == 403){
this.clearCookies();
// try to call this POST method again, but how? And how avoid infinite loop?
}
}
);
}
};
What specific changes need to be made to the code above to handle the 403 error due to server-perceived XSRF-TOKEN and JSESSIONID issues? And how can the post be called a second time after deleting the cookies without leading to an infinite loop in the case where deleting the cookies does not resolve the 403 error?
I am also looking into global approaches to error handling, but there is a combination of public and secure backend REST services, which would need to be handled separately, leading to complexity. This login form is the first point of user entry, and I want to handle it separately before looking at global approaches which would retain a separate handling of the login form using methods developed in reply to this OP.
You could restructure your http calls to auto retry, and use promises in your controllers (or whatever)
var httpPostRetry = function(url, usercredentials) {
var promise = new Promise(function(resolve, reject) {
var retries = 0;
var postRetry = function(url, usercredentials) {
if (retries < 3) {
$http({ method: 'POST', url: '/login1', usercredentials })
.then(function(result) {
resolve(result);
}).catch(function(result) {
retries ++;
postRetry(url, usercredentials);
});
} else {
reject(result);
}
};
}.bind(this));
return promise;
}
and then you would call
httpPostRetry(bla, bla).then(function(result) {
// one of the 3 tries must of succeeded
}).catch(function(result) {
// tried 3 times and failed each time
});
To handle specific http errors you can broadcast that specific error and handle that case in a specific controller. Or use a service to encapsulate the status and have some other part of your code handle the UI flow for that error.
$rootScope.$broadcast('unauthorized http error', { somedata: {} });
Does this help?
Have a look at the angular-http-auth module and how things are done there. I think one key element you would want to use is a http interceptor.
For purposes of global error handling, authentication, or any kind of
synchronous or asynchronous pre-processing of request or
postprocessing of responses, it is desirable to be able to intercept
requests before they are handed to the server and responses before
they are handed over to the application code that initiated these
requests. The interceptors leverage the promise APIs to fulfill this
need for both synchronous and asynchronous pre-processing.
After playing around with interceptors you can look at the angular-http-auth http buffer and the way they handle rejected requests there. If their interceptor receives a responseError, they add the config object - which basically stores all information about your request - to a buffer, and then any time they want they can manipulate elements in that buffer. You could easily adept their code to manipulate the config's xsrfHeaderName, xsrfCookieName, or parameters on your behalf when you receive a 403.
I hope that helps a little.

Get tweets using angularjs

I am trying to make an app with material design and angularjs to get the tweets using hashtag search.
getTweets: function(hashtag, since,$http) {
var cfg = {};
var paramSince = since ? '&since_id='+ since : '';
var queryUrl = 'https://api.twitter.com/1.1/search/tweets.json?q=%23'+hashtag+paramSince;
// var queryUrl = '/search?hashtag='+hashtag+paramSince;
var promise = $http.get(queryUrl, cfg).then(function (response) {
return response;
});
return promise;
}
This API returns error 215, Bad Authentication Data
Here is the full application
STEPS TO REPRODUCE:
(i) Click Add Account
(ii)Login
(iii) Click finish
$http is undefined. You injected $http service into your twitterApp.services factory, then you (try) redeclared it inside the returned function getTweets.
In this case there is no "magic", you call getTweets with two arguments, so $http becomes undefined. The solution is removing this parameter from getTweets and use $http as a closure.
UPDATE:
There's no error handling in the process, you have to reject the promise when error occurs. This way you can also see the error comes from the server.
http://plnkr.co/edit/Lbb6EvwsjuecmFn5Vchd?p=preview
As you can see on the console, when trying to get connected, the server returns an origin error:
Error: Origin "http://run.plnkr.co/Of0F9UHpjhrqkjdw/" does not match
any registered domain/url on oauth.io(…)
It's probably about settings in your server (in this case, oauth.io) in terms of CORS.

angularjs promise not resolving properly

My controller has all the required dependencies injected.
$scope.connect = function(url) {
var defer = $q.defer();
var promise = $http.get(url).then(function (response) {
$timeout(function(){
defer.resolve(response);
},10000);
defer.resolve(response);
$scope.$apply(); //$rootScope.$apply();
});
return defer.promise;
};
$scope.mymethod = function(){
$scope.globalMydata[];
console.log("before the http get");
$scope.connect("MY_URL").then(function(data) {
console.log("called!", data);
console.log("INSIDE the http get");
$scope.mydata = data;
//$scope.globalMydata.push(data);
});
console.log("after the http get ");
//do some processing of the returned data here
var dataHolder = $scope.mydata;
return calculatedValue;//value from procesing
}
When the code is executed "INSIDE the http get" is invoked as the last debug log. I get the results from the get call but since its returned later, I am unable to do any processing with it. This is the exactl reason why we promises right? I need the promised data to do some processing inside the controller.
Any issue in my promise implementation??
I'm not sure whether I get your question, but it looks like you've built a promise interseptor, but from your question it looks like you want just the regular promise behaviour. So I'll try that..
I'm not an angular expert but I do use $http promises a lot and here's how I do it.
I register the $http call as a service, like this:
app.service('ajax',function(host,$http){
this.post = function(api,data,cb){
$http.post(host + api,data).success(cb)
};
this.get = function(api,cb){
$http.get(host + api).success(cb)
}
});
host is a predefined module.value. I inject this service into every controller that needs to call http requests and operate them like this:
ajax.post('users', UserToken, function(data){
console.log('Users points: ' + data.points)
})
As I understand $http has promises built in, so there's no need for q and defere, it's all done at the background. ajax.post calls the service, which sends data to host + 'users', server-side looks for user by his token and return some data with one key by the name of points with a value of the users points. Client-side: upon successful reply from the server it proceeds to the cb function which then console logs the user points.
So I can do whatever modification I need to the promise inside that cb function, since it is not called before a successful reply from the server.
The success and error methods have a few more optional arguments, check them out here.

Categories