Since i'm using Oauth2 to protect my Api, i need to get a new access token before any http requets if the previous access token has expired.
I didn't used event listener much until now.
Here what i did for now (Please let me know if it is correct) :
ApplicationController.js :
app.controller('ApplicationController', function($rootScope, $scope, $localStorage, AuthService){
// Listening event apiRequested
$scope.$on('event:apiRequested', function(e) {
AuthService.token();
// Restore the access_token in case it has changed
access_token = $localStorage.getObject('access_token');
});
})
UserController.js :
$rootScope.$broadcast('event:apiRequested');
// Get Users around
return $http.post(domain+'/api/users?access_token='+access_token.key, data).then(function(response){
return response;
});
First thing i'm not sure about ... Does $http is processed if the event already executed entirely?
So since i'm not sure, i'm thinking about adding a callback.
Here the idea :
$rootScope.$broadcast('event:apiRequested', function(response){
if(response){
// Get Users around
return $http.post(domain+'/api/users?access_token='+access_token.key, data).then(function(response){
return response;
});
}
});
Please let me know if it is possible to do that or should i use something else than event listener for that case.
Why don't you use interceptors that is done to intercept HTTP request ?
In your case, you shall add this very specific behaviour into the "request" part.
See an interceptor exemple bellow:
var $myService; // Add a constant that store the service
$httpProvider.interceptors.push(['$location', '$injector', '$q', function($location, $injector, $q) {
return {
'request' : function(config){
console.log("intercept request", config.url,config)
// Your token shall be retreive in this part
return config
},
'response' : function(config){
$myService= $myService|| $injector.get('$myService'); // inject the service manually if constant is undefined
console.log("intercept response", config)
// Your token shall be retreive in this part
return config
},
'responseError': function(rejection) {
console.log("responseError intercepted" , rejection);
if (rejection.status === 403) {
return $q.reject(rejection);
} else if (rejection.status === 423) {
return $q.reject(rejection);
}else
return $q.reject(rejection);
}
};
}]);
Interceptors shall be defined into .config(["$httpProvider", function($httpProvider)
Related
I'm writing an ionic v1/express/mongo/node app. When checking if the user is authenticated i have the following piece of code:
checkAuthentication: function(token) {
console.log(token);
return $http.get("http://validUrl/test", {
headers: {
'testingAuth': token
}
}).then(function(result) {
return result.data;
});
}
and am calling it like this:
checkAuthentication(token).then(function(response) {
console.log("testing response: " + response);
// if a valid token is already in storage = response contains "Success"(?), just $state.go to main page, else call the login() function
if (response.content === "Success") {
// $state.go main page
} else {
console.log("could not log in");
}
})
The problem is, when I get back code 401 from the server, I somehow skip the then block in the checkAuthentication function. Execution doesn't stop at a breakpoint at "return result.data", or "console.log("could not log").
Am I doing something obviously wrong? Is there something i need to do to force going into that block? Thanks for any advice.
The issue is with your error handling ! I took the liberty to modify your code and the way to do a $http call. Here is the working plunker for the same.
If you observe the $scope.login() function I've injected the service object and invoked the checkAuthentication() function which does the HTTP call.As you are using .then for http calls, angular provides provision to use two functions for success and error callbacks where your HTTP errors go when HTTP calls fail.
Here is the angular doc for the same.
In your example you don't have error callback method hence it doesn't go into your if else conditions.
var app = angular.module("App", []);
app.controller('AppCtrl', ['$rootScope', '$scope', '$http', 'Service', function($rootScope, $scope, $http, Service) {
$scope.login = function(token) {
//call the injected service in the function for HTTP call..
Service.checkAuthentication(token).then(function(response) {
console.log("testing response: " + response);
// if a valid token is already in storage = response contains "Success"(?), just $state.go to main page, else call the login() function
if (response.content === "Success") {
// $state.go main page
}
},function(error){
console.log(error);
console.log("could not log in due to error...");
});
};
}]);
//use angular services to do a http call for better code maintainability...
app.service('Service', ['$http', '$rootScope', function($http, $rootScope) {
return {
checkAuthentication: function(token) {
return $http.get("http://validUrl/test", {
headers: {
'testingAuth': token
}
});
}
};
}]);
I have an application for which I created an interceptor to handle token expirations after 15 minute inactivity, it successfully redirects to the login page after a token has expired, but Im not able to show the error after redirecting to the login page.
My question is, how can I show the user the token expired error on the login page, after the interceptor has redirected the app to that page.
Heres my redirector:
app
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function($q, $location, LoopBackAuth) {
return {
responseError: function(rejection) {
if (rejection.status == 401) {
//Now clearing the loopback values from client browser for safe logout...
LoopBackAuth.clearUser();
LoopBackAuth.clearStorage();
$location.path("/login");
}
return $q.reject(rejection);
}
};
})
}])
.config(function(LoopBackResourceProvider) {
LoopBackResourceProvider.setAuthHeader('X-Access-Token');
})
Finally and thanks to #forrestmid to point me in the right direction this is what I ended up doing.
on the http interceptor just added:
$location.path("/login").search({error: 'invalid_token'});
and then on the controller just had to do:
var queryString = $location.search();
$scope.errors = {};
if (queryString && queryString.error) {
$scope.errors = {
'invalid_token': {
code: 'invalid_token'
}
}
}
now on the template I already have logic to handle the error object so now it works fine :)
Referencing this post in regards to injecting the $state service into an HTTP interceptor:
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function($q, $injector, LoopBackAuth) {
return {
responseError: function(rejection) {
if (rejection.status == 401) {
//Now clearing the loopback values from client browser for safe logout...
LoopBackAuth.clearUser();
LoopBackAuth.clearStorage();
$injector.get('$state').go('app.login', {error: 'Token expired.'});
}
return $q.reject(rejection);
}
};
})
}]);
Assuming that you're using ui.router:
app.config(function($stateProvider){
$stateProvider
.state("app",{abstract: true})
.state("app.login", {
url: "/login",
params: {error: ""}
});
});
By default there will be no error when transitioning to the app.login state, but when there is a param error set to whatever, it can display the error. This will be in the $stateParams.error variable on your login page.
Let me know if I botched any of that code since I didn't test it. The line I think you want is the $injector line.
I have simple api and a authorization point
when i request to api i get a 401 if the token is invalid (token loses validity past five minutes).
i know i can intercept 401 for example with
app.factory("HttpErrorInterceptorModule", ["$q", "$rootScope", "$location",
function($q, $rootScope, $location) {
var success = function(response) {
// pass through
return response;
},
error = function(response) {
if(response.status === 401) {
// dostuff
}
return $q.reject(response);
};
return function(httpPromise) {
return httpPromise.then(success, error);
};
}
]).config(["$httpProvider",
function($httpProvider) {
$httpProvider.responseInterceptors.push("HttpErrorInterceptorModule");
}
]);
but i want capture and queue the request and show a login form if is success then change the token (it's a header) and execute request again
You can use $httpInterceptor in slightly another way. If you want to redirect user after login to page where user actually failed you need to cache failed request in some service and then redirect user somewhere after login (I beleive in logic connected to your login).
But you may need to have some test endpoint to protect your controllers from unrestricted access, you might want to use resolve https://thinkster.io/egghead/resolve/
So in this case you will receive error connected with restricted access to proctedted endpoint but not to your page.
To solve this problem I used marker param (or header) to find out where I should redirect user after login.
Here is example of your httpInterceptor.
angular.factory('httpInterceptor', function ($q, $rootScope, $log, someService) {
return {
request: function (config) {
return config || $q.when(config)
},
response: function (response) {
return response || $q.when(response);
},
responseError: function (response) {
if (response.status === 401) {
//here I preserve login page
someService
.setRestrictedPageBeforeLogin(
extractPreservedInfoAboutPage(response)
)
$rootScope.$broadcast('error')
}
return $q.reject(response);
}
};
})
.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
});
angular-http-auth module provides a service that intercepts requests and queques them to re-send them later once a user logs in.
This service fires also these events below, so you could listen to them and decide what to show on screen
event:auth-loginRequired
event:auth-loginCancelled
event:aut-loginConfirmed
Look at the code. It has just a few lines of code
https://github.com/witoldsz/angular-http-auth
With angular $resource, I would like to fire a callback function when a request is successfully sent to the restful backend. (The backend may take a long time and I only want to know if it received the data I sent.)
The only thing I've found so far is resource.action.$promise['finally'](callback);
I'd be also interested to know when the request could not be sent. (eg. connection problems)
Thanks!
Here is DRY approach :
Build a service intercepting every HTTP requests (like the one defined in the official documentation) :
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return {
// optional method
'request': function(config) {
// do something on success
return config;
},
// optional method
'requestError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
},
// optional method
'response': function(response) {
// do something on success
return response;
},
// optional method
'responseError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
}
};
});
Simply put your code inside the desired hooks. You could for instance draw an error modal dialog if the request fails.
Finally, Register it to your application :
app.config(['$httpProvider', function($httpProvider) {
// Add the interceptor to the $httpProvider to intercept http calls
$httpProvider.interceptors.push('myHttpInterceptor');
}]);
So I have a bunch of controllers that do $http requests
but in every $http request i have a .error(function(data...){//always the same})
How could I build an.. "abstract class" for $http?
This here would be the always repeating code
.error(function(){
$scope.flashes = {
server: {
type: "danger",
message: "There was a server error processing your request. Please try again later."
}
};
})
I add the same concern few weeks ago and i came up with this solution :
I first created a custom service intercepting every http requests made :
.factory('HttpInterceptor', ['$q', '$rootScope', function($q, $rootScope) {
return {
// On request success
request : function(config) {
// Return the config or wrap it in a promise if blank.
return config || $q.when(config);
},
// On request failure
requestError : function(rejection) {
//console.log(rejection); // Contains the data about the error on the request.
// Return the promise rejection.
return $q.reject(rejection);
},
// On response success
response : function(response) {
//console.log(response); // Contains the data from the response.
// Return the response or promise.
return response || $q.when(response);
},
// On response failure
responseError : function(rejection) {
//console.log(rejection); // Contains the data about the error.
//Check whether the intercept param is set in the config array. If the intercept param is missing or set to true, we display a modal containing the error
if (rejection.config && typeof rejection.config.intercept === 'undefined' || rejection.config.intercept)
{
//emitting an event to draw a modal using angular bootstrap
$rootScope.$emit('errorModal', rejection.data);
}
// Return the promise rejection.
return $q.reject(rejection);
}
};
}]);
I also defined a custom config property 'intercept' that i can add to the $http config object. It is useful when I don't want to apply this behavior on a particular request.
E.g :
var registerResource = $resource('/registration/candidate/register', {}, {query:
{method:'POST', isArray: false, intercept: false }
});
In order the have a flexible solution, it is also important to not forget to do :
return $q.reject(rejection);
So you can still use the error callback on your promise in your controller if you want to combine both ways (interception + manual handling)
Finally, I added this service to my application :
app.config(['$httpProvider', function($httpProvider) {
// Add the interceptor to the $httpProvider to intercept http calls
$httpProvider.interceptors.push('HttpInterceptor');
}]);
I simplified the service but you can also use it for many things. Personally, I also use it to :
Make sure to not fire duplicate http requests (if the user click a lot on a submit button).
Draw an alert at the beginning of an http call and close it at the end to inform the user that is treatment is processing (export of data for instance).
PS: The official documentation mention this interceptor
You could do something like this:
app.service('myHttp', function($http){
return function($scope, httpParameters){
var httpPromise = $http(httpParameters);
httpPromise.error(function(){
$scope.flashes = {
server: {
type: "danger",
message: "There was a server error"
}
}
});
};
});
app.controller('MainCtrl', function($scope, myHttp) {
myHttp($scope, {method: 'GET', url: 'www.google.com'});
});