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.
Related
I'm working on an AngularJS 1.5.3 project.
There is one page in my app that needs some server information before going to the page. It's kind of like a route guard. No other pages need this information before proceeding.
How can I get this to work in my route resolve? Is this a valid approach to this problem?
e.g.
.state('verifyCredentials', {
url: '/verifyCredentials',
templateUrl: 'verifyCredentials/verify-credentials.html',
controller: 'VerifyCredentialsController',
controllerAs: 'verifyCredentialsController',
resolve: {
//calls http request
authentication
.getInfo()
.then(function(response) {
if(response.goToHome === true) {
//$state.go('home);
} else {
//proceed to verify credentials
}
})
}
})
An AngularJS (1.x) resolve: block is an object on a state definition. Each key is the name of some data to load, and each value is a function which returns a promise for the data.
Resolve functions are injected using Angular’s injector.
An example can be found in the ui-router tutorial.
You are not providing a valid object to your resolve. The authentication should be a value to a key. Here is how your resolve should look.
.state('verifyCredentials', {
url: '/verifyCredentials',
templateUrl: 'verifyCredentials/verify-credentials.html',
controller: 'VerifyCredentialsController',
controllerAs: 'verifyCredentialsController',
resolve: {
authenticate: function(authentication, $state) { // you'll need to inject $state
and your authentication service here if you want to use them.
return authentication
.getInfo()
.then(function(response) {
if(response.goToHome === true) {
$state.go('home');
} else {
//proceed to verify credentials
}
});
}
})
Another possibility is to use the redirectTo method on the state object in order to redirect to a different state depending on the resolve.
Use the redirectTo with the resolve like so:
.state('verifyCredentials', {
resolve: {
authResolve: function(authenticate) {
return authentication.getInfo();
}
},
redirectTo: (trans) => {
// getAsync tells the resolve to load
let resolvePromise = trans.injector().getAsync('authResolve')
return resolvePromise.then(resolveData => {
return resolveData.loggedIn === true ? 'home' : null; // go to home if logged in, else stay on this route in order to continue with credentials flow.
)}
})
Here is how the doc recommend to handle resolves. They use component architecture.
.state('verifyCredentials', {
url: '/verifyCredentials',
component: 'verifyCredentialsComponent',
resolve: {
authenticateData: function(authentication, $state) { // you'll need to inject $state
and your authentication service here if you want to use them.
return authentication
.getInfo()
.then(function(response) {
if(response.goToHome === true) {
$state.go('home');
} else {
//proceed to verify credentials
}
});
}
})
// component
angular.module('app').component('verifyCredentialsComponent', {
bindings: {
authenticateData: '<'
},
template: '<div></div>'
controller: function() {
...
}
})
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";
}
}
},
});
});
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);
}
};
}
}());
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 have a SPA using JWT (json web tokens) for authorization to the api. The problem is when they hit refresh on the browser after being logged in I need to verify the token is still valid via an ajax request and then continue loading the SPA. I added this to the .run() which kind of works, but since my navigation changes if they are logged in, the page loads before the token is verified, and looks wrong. I'm new to angular, but guess this could be done with a promise?
// handle refreshing browser
if ($window.sessionStorage.token && !AuthenticationService.isLogged()) {
UserService.verify().success(function (data) {
AuthenticationService.setLogged(true);
}).error(function () {
delete $window.sessionStorage.token;
$state.transitionTo('app.login');
});
}
I was able to get this working with the following state setup:
$stateProvider
.state('app', {
abstract: true,
url: '/',
views: {
root: {
templateUrl: 'tpl/index.html'
}
},
resolve: {
User: ['$window', '$q', 'AuthenticationService', 'UserService' , function($window, $q, AuthenticationService, UserService) {
if ($window.sessionStorage.token && !AuthenticationService.isLogged()) {
var d = $q.defer();
UserService.verify().success(function (data) {
AuthenticationService.setLogged(true);
d.resolve();
}).error(function () {
delete $window.sessionStorage.token;
d.resolve();
});
return d.promise;
}
}]
}
});