The basic idea was to use an Http Response Interceptor to redirect my webpage if it gives an 401 status. But i don't know if i am doing this the right way: i thought it was something like this but i seems more difficult than it seems. At the moment i get an Circular dependency found.
Do i need to push the interceptor somewhere else? And how can the interceptor know if i get an 401 request. is it also possible to define which 401 needs to be intercept and which ones ignored
(function () {
'use strict';
angular
.module('app', ['ngRoute','ngCookies','ngMessages'])
.config(routeConfig);
routeConfig.$inject = ['$routeProvider','$httpProvider'];
function routeConfig($routeProvider,$httpProvider) {
$routeProvider
.when('/', {
templateUrl: 'login.html',
controller : 'LoginController'
})
.when('/register', {
templateUrl: 'register.html',
controller : 'RegisterController'
})
.when('/main', {
//This gives an 401 status when the user has not been logged in
templateUrl: 'home.html',
controller : 'HomeController'
})
.otherwise('/');
// We need to add this for csrf protection
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
//this gives an Circular dependency error
$httpProvider.interceptors.push('HttpResponseInterceptor');
}
})();
This is my service:
(function () {
'use strict';
angular
.module('app')
.factory('HttpResponseInterceptor', HttpResponseInterceptor);
HttpResponseInterceptor.$inject = ['$rootScope','$http','$q','$location'];
function HttpResponseInterceptor($rootScope,$http,$q,$location) {
return {
response: function(response){
if (response.status === 401) {
}
return response || $q.when(response);
},
responseError: function(rejection) {
if (rejection.status === 401) {
$location.path('/login');
}
return $q.reject(rejection);
}
};
}
})();
Update2
As mention in the comment i was injecting to much stuff. So this is one problem fixed, but now when i go to the login page it makes a request on loading the page (localhost:8080/user) which results in an infinite loop of redirection to the login page and with a browser crash as result.
So is there a way i can say to the Interceptor which url's needed to be redirected and which ones don't
This is answer to 2nd problem ...
You query the rejection object and add some further conditions to your IF statement ...
you can dump rejection object with ...
console.log(JSON.stringify(rejection));
then add conditions to your IF ...
if (rejection.status === 401 && rejection.config.url !== "/url/you/do/not/want/to/change/state/on") {
// do state.go or something else here
}
does injecting services using $injector help?
(function () {
'use strict';
angular
.module('admin')
.factory('HttpInterceptor', httpInterceptor);
httpInterceptor.$inject = [
'$q', // return promises
'$injector' // login service injection
];
function httpInterceptor(q, injector) {
return {
responseError: function (rejection) {
var url = rejection.config ? rejection.config.url : undefined;
var state = injector.get("$state");
if (rejection.status === 401) {
if (!(url === "/api/logout" || state.current.name === "login" && url === "/api/authenticated")) {
var loginService = injector.get('LoginService');
loginService.logout();
}
}
if (rejection.status === 403) {
state.go("home");
}
return q.reject(rejection);
}
};
}
}());
Related
Sample code snippet
module.config(function($stateProvider, $couchPotatoProvider){
$stateProvider.state('app.securePage', {
url: '/secure-page',
views: {
'foo': {
controller: 'SecureCtrl',
templateUrl: 'app/modules/templates/secure-page.html',
resolve: {
deps: $couchPotatoProvider.resolveDependencies([
'modules/common/service/Profile', #Have access to all
'modules/common/service/List', #Have access to all
'modules/secure/service/Admin' #Restricted access - throws 403 response whenever unauthorized user tries to access
])
}
}
}
})
})
// In HTTP Interceptor
app.factory('httpInterceptor', function ($q, $rootScope, $log, $window) {
return {
...
responseError: function (response) {
if (response.status === 403) {
document.location.href = $rootScope.baseUrl+'/dashboard';
}
return $q.reject(response);
}
};
})
The problem here is, unauthorized users getting secure url from somewhere and trying to access the page but initially template is loading after a second we redirecting to some other page. Here I want to avoid loading template until all dependencies resolved.
I have been trying to intercept 401 responses when I hit an API but it has been frustratingly not working at all.
When I visit a particular link, on the browser console, I can see that it is responding with 401. Basically I want to redirect it to my login page whenever it encounters such responses. I have seen a lot of the questions here and tried to do something similar but it is not working.
Here is the contents of my main.js file.
var app = angular.module('strelsau_client', ['ngRoute', 'ngResource', 'angularCSS']).config(function ($provide, $httpProvider) {
// Intercept http calls.
$provide.factory('MyHttpInterceptor', function ($q) {
return {
// On request success
request: function (config) {
console.log(config); // Contains the data about the request before it is sent.
// 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 failture
responseError: function (rejection) {
console.log(rejection); // Contains the data about the error.
// Return the promise rejection.
return $q.reject(rejection);
}
};
});
// Add the interceptor to the $httpProvider.
$httpProvider.interceptors.push('MyHttpInterceptor');
});
app.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/bookings/fare.html',
controller: 'BookingsCtrl',
css: ['../assets/css/stylesheet.css']
})
.when('/admin',{
templateUrl:'views/admin/index.html',
controller: 'AdminCtrl',
css: ['../assets/css/vendor.css','../assets/css/flat-admin.css']
})
.when('/admin/taxis',{
templateUrl:'views/admin/taxis.html',
controller: 'AdminCtrl',
css: ['../assets/css/vendor.css','../assets/css/flat-admin.css']
})
.when('/admin/customers',{
templateUrl:'views/admin/customers.html',
controller: 'AdminCtrl',
css: ['../assets/css/vendor.css','../assets/css/flat-admin.css']
})
.when('/admin/set_price', {
templateUrl: 'views/admin/set_price.html',
controller: 'AdminCtrl'
})
.when('/admin/adddriver', {
templateUrl: 'views/admin/add_taxi_driver.html',
controller: 'AdminCtrl'
})
.when('/signup', {
templateUrl: 'views/signup.html',
controller: 'AccountCtrl'
})
.otherwise({
redirectTo: '/'
});
});
I'm gonna post my working code below so you can compare and adjust yours:
angular.module("application").config(['$httpProvider' ,function($httpProvider) {
$httpProvider.interceptors.push(['$rootScope','$q','$location','$injector', function($rootScope, $q, $location, $injector) {
return {
responseError: function(rejection) {
var $state = $injector.get('$state');
if(rejection.status <= 0) {
}else if (rejection.status == 401) {
// Here I pick my 401
}else if (rejection.status == 404) {
//$state.go('404');
}else if (rejection.status == 500) {
$state.go('500');
}
return $q.reject(rejection);
}
};
}
]);
}
]);
Hope it helps =)
I finally did the interception at the service level. Here is how I did it :
app.service('AdminService', function($resource) {
return $resource('http://localhost:3000/admin/customers',{},{
query:{
method: 'GET',
interceptor: {
response:function(data){
console.log("success");
},
responseError: function(data){
window.location="/#/admin/login";
}
}
},
});
});
EDIT: forgot to mention that i've been working with AngularJs for a week only, so if you see something you think should be changed for the better and is not related to the question itself feel free to tell me on the comments section.
ok, so I have my authentication Controllers and providers which I won't show because they're irrelevant for the scope of the question.
Then I have an interceptor to check if the user is authenticated when a Call is made. If so I set the Authentication header on the request to include the user's Token if not I redirect the user to the login page and don't even make the request to the server (obviously if someone bypasses this theres also an Authorize on the API).
What I want is to add a few exceptions, meaning there are some pages I want to allow even if the user has no Auth Token. I'm able to this if it's a specific path, but I want to allow my 404 page to be accessed and it's in the Routing that I'm specifying .otherwise to go to the 404 page, how can I make so that my interceptor only redirects to login if it's not going to this page.
The interceptor
.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var authData = localStorageService.get('authorizationData');
var _request = function (config) {
config.headers = config.headers || {};
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
} else if ($location.path != '/accounts/login' && $location.path != '/accounts/register') {
$location.path('/accounts/login');
}
return config;
}
var _responseError = function (rejection) {
if (rejection.status === 401) {
$location.path('/accounts/login');
}
return $q.reject(rejection);
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
return authInterceptorServiceFactory;
}])
and in my Routing
$urlRouterProvider.otherwise('/page-not-found');
$stateProvider
(...)//rest of the states
.state('page-not-found', {
url: '/page-not-found',
templateUrl: '/Content/partials/error/404.html',
data: {
displayName: false
}
})
(...)//rest of the states
I tried to add '/page-not-found' to my if but it won't work as expected because by the time the location is checked for the first time it's still not redirected.
edit
As sugested by charlietfl I'm now trying to use resolve but it's not even passing my function.
I removed this code from my interceptor:
else if ($location.path != '/accounts/login' && $location.path != '/accounts/register') {
$location.path('/accounts/login');
}
and add a new service to the authentication module:
.service('authCheckService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) {
var self = {
'onlyLoggedIn': function ($state, $q) {
var deferred = $q.defer();
var authData = localStorageService.get('authorizationData');
console.log(authData);
if (authData) {
deferred.resolve();
} else {
deferred.reject();
$state.go('login');
}
return deferred.promise;
}
}
return self;
}]);
and i'm trying to call it as:
.state('smo-dashboard', {
url: '/dashboard',
templateUrl: '/Content/partials/dashboard.html',
resolve: authCheckServiceProvider.onlyLoggedIn
})
notice that i'm trying to log authData var to check if it's working but it isn't and there's no error on the console also.
Finally figured out how to solve it using resolve.
first of all I completely removed the interceptor I was using before.
then I made a function inside my Routing .config to use with every resolve for the authentication. finally to handle my resolve I'm using $stateChangeError to redirect to the login state
the Routing Config
.config(function ($stateProvider, $urlRouterProvider) {
// function to check the authentication //
var Auth = ["$q", "authService", function ($q, authService) {
authService.fillAuthData;
if (authService.authentication.isAuth) {
return $q.when(authService.authentication);
} else {
return $q.reject({ authenticated: false });
}
}];
/* if the state does not exist */
$urlRouterProvider
.otherwise('/page-not-found');
$stateProvider
// state that allows non authenticated users //
.state('home', {
url: '/',
templateUrl: '/Content/partials/home.html',
})
// state that needs authentication //
.state('smo-dashboard', {
url: '/dashboard',
templateUrl: '/Content/partials/dashboard.html',
resolve: {
auth: Auth
}
})
// errors //
.state('page-not-found', {
url: '/page-not-found',
templateUrl: '/Content/partials/error/404.html'
})
// accounts //
.state('login', {
url: '/accounts/login',
templateUrl: '/Content/partials/account/login.html'
})
// OTHER STATES //
}
);
in the MainController
$scope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) {
$state.go("login");
});
An error service like this could help to handle what to do according to status in responses:
'use strict';
/**
* Error Service
*/
angular.module('app.errorService', [])
.factory("errorService", function ($route, $location) {
return {
checkAndReturnError: function(a,b,c) {
if (a.status === 401){
(function(){
return $location.path("/accounts/login");
}());
return;
}
if (a.status === 404)
return;
alert("Error \n *" + a.data.message);
}
};
});
Then when you do your calls if the response status is 401 it will redirect. The vbad thing agout this is you have to add it to all calls:
$scope.pageChanged = function() {
$scope.Promise = Resource.get({}, function(response) {
}, errorService.checkAndReturnError);
};
I use the 401 error handling globally within my AngularJS app.
The code below works, the next step for me is to implement the login functionality. Let's say it's done - but how to redirect the the path placed inside the redirectTo param after successful login ?
I can store that redirectTo path in the cookies or in the $scope variable - but I'd like to achieve this without any data storing.
myApp.factory('authHttpResponseInterceptor', ['$q', '$location', function ($q, $location) {
return {
response: function (response) {
if (response.status === 401) { }
return response || $q.when(response);
},
responseError: function (rejection) {
if (rejection.status === 401) {
var path = $location.path();
$location.path('/login').search('returnTo', path);
}
return $q.reject(rejection);
}
}
}]);
....
$httpProvider.interceptors.push('authHttpResponseInterceptor');
then my URL looks like http://localhost:1234/app#/login?returnTo=%2Fedit-data%2F32
I'm trying to configure the $http service of Angular, to redirect to an URL when the status code is 403.
No problems so far but the URL to redirect to is coming from the server, through a service which is using $http (obiously).
Here's a piece of code:
angular
.module('app')
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(['$q', 'URLs',
function($q, Redirect) {
return {
request: function(config) {
return config || $q.when(config);
},
responseError: function(response) {
if(response.status === 403) {
// redirect to URLs.login
}
return $q.reject(response);
}
};
}
]);
}])
.factory('URLs', ['$http', function($http) {
var URLs;
$http.get('/urls').then(function(response) {
URLs = response.data;
});
return URLs;
}]);
This code is creating a circular dependency (error) in Angular.
Is there a way that I can do this, having dynamic URLs that are coming from a server and based on this to redirect the user to one of them when the response.status is 403?
Use $injector service to lazily load the URLs service:
angular
.module('app')
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(['$q', '$injector',
function($q, $injector) {
return {
request: function(config) {
return config || $q.when(config);
},
responseError: function(response) {
var Redirect = $injector.get('URLs');
if(response.status === 403) {
// redirect to URLs.login
}
return $q.reject(response);
}
};
}
]);
}])
You can also break this circular dependency in the URLs service by injecting the $injector there.