In my angularjs app I have route /#/tasks, which fetch tasks from /tasks url. If user manually goes to /tasks url, he gets json data. Should I prevent show json data to user? What is the best practice? For example, in backend I can check if request is ajax or not. If not - redirect to base url. Backend - laravel 5.
From the title of the post having the keywords 'prevent user', it seems that you need some sort of logic to define access privileges for a particular user.
You could stick to resolving the authorization logic before a user ever accesses a route.
For this, you can take advantage of the $routeProvider's resolve property of the .when() method.
According to the docs, the resolve property is ...
An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated.
If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired.
The resolve takes in functions (as injectable dependencies) each of which may return a promise, and if any of the promises get rejected, as quoted, an error is thrown and literally, neither the route nor the view is loaded.
For the authorization logic to work, you'll need to perform some API call to some backend function/service which either resolves that the user is permitted for the route, or rejects his/her request.
.when('/tasks', {
resolve: {
authorize: function(authService){
//some api call to get either acceptance (e.g. status code 200) or rejection (status code 500) for the user being authorized
return authService.authorize();
}
},
controller: function(){
//route controller logic
}
})
Alternatively, if you'd also want to perform something on failure of the promise, you could do so by simply chaining the promise with a catch() handler and throw an error to forcefully reject the promise that is returned from the catch() itself.
.when('/tasks', {
resolve: {
authorize: function($location, authService){
//some api call to get either acceptance or rejection for the user being
return authService.authorize()
.then(function(){
//some logic on success of the promise
})
.catch(function(){
$location.path('/auth/login/');
throw 'Authorization error';
});
}
},
controller: function(){
//route controller logic
}
})
Related
I have a service named loginManager which stores objects called is_logged_in & api_token along with few others. My various controllers make ajax calls using $http using the api_token.
If the api_token is reset/expired on server, response is sent as auth_error, at this point I set is_logged_in = false
What i want to achieve is, whenever is_logged_in is changed, the service redirects to /login using $location.path('/login'), i.e. to say, I want to watch the object inside the service, and invoke callback on change from service itself.
I just want the service to take care of login and corresponding routing, without any controller worrying about weather user is logged in or not.
I believe Pankaj Pakar's answer could work but you should use angular's interceptors for that. They intercept all messages. You could add hook for response or responseError and when you recieve auth_error you do any action you like. For example $location.path('/login'), display error to user, etc.
If you want to separate logic you could inject your service with all code inside and just call some method on it.
I'd suggest you to put that watcher in run phase on the angular application which will be there at a single place, by which you could check the value is_logged_in flag of service & if user is not login then redirect him/her to login page directily.
Code
app.run(function($rootScope, loginManager, $location){
$rootScope.$watch(function(){
return loginManager.is_logged_in;
}, function(newValue){
if(angular.isDefine(newValue) && !newValue)
$location.path('/login');
//$state.go('login'); //if you are using ui.router
})
})
Edit
Really curious part of your question is, from where you are changing is_logged_in flag of your service as #JBNizet asked? If any code is there is JavaScript then you should directly redirect to login page from there.
I feel the need to answer something more, Mior is quite right, but his answer needs more meat.
Here I show you how I managed to handle ALL server XHR requests with response 401 unauthorized.
First of all you need a service:
'use strict';
angular.module('theModule')
.factory('interceptorService', ['$q', '$location', function ($q, $location) {
return {
response: function (response) {
return response || $q.when(response);
},
responseError: function (rejection) {
var returnTo = $location.path().replace(/^\/|\/$/g, '');
if (returnTo === 'login') {
return;
}
if (rejection.status === 401) {
console.log('Unauthorized');
$location.path('/login').search('returnTo', returnTo);
}
return $q.reject(rejection);
}
};
}]);
This will be used to intercept all XHR calls and to change the location every time a 401 error is found.
I've also added an improvement that is the "returnTo" parameter, you will be able to use it after login to return to the previous page.
To bind it to each request you have to call the config method, this is my main javascript.
'use strict';
/**
* #author Gianmarco Laggia
*
* Main module of the application an configurations.
*/
angular
.module('theModule', [])
.config(['$httpProvider', function ($httpProvider) {
//Http Interceptor to check auth failures for xhr requests
$httpProvider.interceptors.push('interceptorService');
}]);
This is pretty much what you need to intercept every request, working on the rejection.status you can also intercept events such as server down (status is -1), internal server error (500+), success status (in the response part, status 200+) etc.
I'm a bit confused how to chain promises in AngularJS. I have a $resource implementation... This is some simplified code to get my point:
myResource
.save({ id: 123 })
.$promise
.then(function(data) {
// redirect to root on success
$location.url("/");
})
.catch(function() {
console.log("Failed");
})
.finally(function() {
// reenable form
$scope.enabled = true;
})
What I'm confused about is whether my .catch() function will be called when my resource will fail saving or will it only catch errors that would originate in .then() function or maybe both?
When chaining these how can I have a single .catch (before .finally) that would catch all errors of calls that happened in the chain proceeding it?
Note: I'm aware I could provide the success and error functions in .save function but I have other parts to handle as well.
First of all, read the docs of Angular's implementation of promises.
Promises can be resolved in two ways - either successful (the resolve method) or not (the reject method).
When resource could not be saved (i.e. server did not respond with 2XX status), then your catch method will be called, as the promise will be rejected.
Also please note, that your catch handler should reject the promise for next possible handlers. When you return non-rejected value from your catch method, it is assumed that the error has been handled and the promise can call subsequent then methods, if any. Therefore, if there is any possibility that the promise is used somewhere else, your code should look like this:
// inject $q
myResource
.save({ id: 123 })
.$promise
.then(function(data) {
// redirect to root on success
$location.url("/");
})
.catch(function() {
console.log("Failed");
return $q.reject();
})
.finally(function() {
// reenable form
$scope.enabled = true;
})
Also notice that you made a typo in the then method - you wrote "ulr" instead of "url".
I followed this post to implement a similar ajax loader image on a project:
Implementing loading spinner using httpInterceptor and AngularJS 1.1.5
My implementation has a few differences:
I use $rootScope to emit and not broadcast and I also use $rootScope on the directive to handle the event.
Because of a particularity of the project, I have to unbind the directive $rootScope.$on listeners right after the first event being fired (either for show or hide), inside the event handler.
I only fire a single show/hide event. Show on the first HTTP request, hide when the count reaches 0.
I believe those are the major differences from the linked post, not sure if they are relevant to my question but just in case they are...
When the loader hide event is handled, the loader is gone and I won't show it again unless the page is refreshed, but I still have background http requests to refresh data on the current page. Those requests will still be intercepted and fire new show/hide events, which are no longer required/handled. I only need a first show and first hide, that's it.
What's the right way to remove the HTTP interceptor I added to the $httpProvider after the the first hide event has been fired?
I know we add the interceptor using a $httpProvider.interceptors.push() but I'm not sure how to pop it out when I no longer need that interceptor.
I was going to put a bounty on this, as I had the same question. However....it looks as though the interceptors and responseInterceptors are simply arrays, according to the source code (lines 127 and 133 in the $httpProvider factory). There's no wrapper around this.
From what I can tell, you would either have to use pop() or any other array method. However, this means that you don't know what you're popping! Holding a reference to the object wouldn't really help, because you can't really do an array function on it, unless you decide to iterate based on equality (which could work, using indexOf or something else like Underscore).
Really, what Angular needs is a wrapper for this since you can't be sure that your interceptor is the last one on the list.
The best solution that I've found is the one explained by jedd.ahyoung in his comment.
These are the steps.
Add two custom factories
angular.module('myModule.services', [])
/**
* Save application information.
*/
.factory('Application', function($log) {
return {};
})
/**
* Custom Http Interceptor for the loader.
*/
.factory('myHttpInterceptor', function($rootScope, $q, Application) {
return {
request: function(config) {
if(Application.enableLoader){
$rootScope.$broadcast('loading:show');
}
return config;
},
requestError: function(rejection) {
$rootScope.$broadcast('loading:hide');
return $q.reject(rejection);
},
response: function(response) {
$rootScope.$broadcast('loading:hide');
return response;
},
responseError: function(rejection) {
$rootScope.$broadcast('loading:hide');
return $q.reject(rejection);
}
};
});
Add it in your config step
.config(function($httpProvider) {
//loading interceptor
$httpProvider.interceptors.push('myHttpInterceptor');
});
Enable/disable it when/where you want
Application.enableLoader = true;
$http({
url: url,
method: "GET"
}).success(function(data){
$log.log("Data received and my loader is already closed :)");
Application.enableLoader = false;
$scope.retrieveBooks();
}).error(function(){
$log.log("Error, but my loader is already closed :)");
Application.enableLoader = false;
$scope.retrieveBooks();
});
$scope.retrieveBooks = function(){
$http({
url: url,
method: "GET"
}).success(function(data){
$log.log("Data received and my loader is not closed :(");
}).error(function(){
$log.log("Error, but my loader is not closed :(");
});
};
I want to use an interceptor for status code error handling. For example, when I'm loading an template through $route and I don't have the permission to do so I'm sending an 403 status code back from the server together with some html data, which I want to display instead of the originally requested template. Is this possible inside my interceptor in the 'responseError' function?
'responseError': function(rejection) {
// do something on error EXAMPLE
if (canRecover(rejection.htmlData)) {
return responseOrNewPromise //here I want to add my server error html data and return that to the client instead of the originally requested data
}
return $q.reject(rejection);
}
I know, that I could do a location.path change but i want to save the extra routing.
From ngRoute docs:
$routeChangeError
Broadcasted if any of the resolve promises are rejected.
Add an event listener to when a promise resolve is rejected
app.run(function($rootScope){
$rootScope.$on('$routeChangeError',function(angularEvent,current,previous,rejection){
// console.log(rejection)
})
})
I also recommend these tutorials:
https://egghead.io/lessons/angularjs-resolve-routechangeerror
https://egghead.io/lessons/angularjs-directive-for-route-handling
Now I know that because of the way javascript executes it is recommended that you run all remote requests as async instead of sync. While I agree with that 99% of the time, sometimes you do want to run remote request as a sync instead of a async. For example, loading session data is something I would want to do synchronically as I don't want any views to render until that data is loaded. This plunker shows the issue with loading session data asynchronically (NOTE: I am using $timeout to simulate what would happen with an async call):
http://plnkr.co/edit/bzE1XP23MkE5YKWxRYrn?p=preview
The data property does not load anything because the data is not available when it tries to get it and data2 does only because the data is available when it tries to get it. Now in this case I could just put the session variable on the scope and be done with it but that is not always the case.
Is there a better way to do sync remote calls in an angular application other than using jQuery's .ajax() method (trying to depend on jQuery as little as possible)?
If you want the session data to be loaded prior to a controller being loaded, you should included it as as resolve parameter (assuming you are using the $routeProvider).
For example:
angular.module('mymodule', ['ngResource'])
/* here's our session resource. we can call Session.get() to retrieve it. */
.factory('Session', ['$resource', function($resource) {
return $resource('/api/session.json');
}])
/* here's our controller + route definition. */
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/foo', {
controller: 'MyCtrl',
templateUrl: '/templates/foo.html',
/* the controller will not be loaded until the items
* below are all resolved! */
resolve: {
session: ['$q', 'Session', function($q, Session) {
var d = $q.defer();
Session.get(function(session) {
/* session returned successfully */
d.resolve(session);
}, function(err) {
/* session failed to load */
d.reject(err);
});
return d.promise;
}]
}
});
}])
.controller('MyCtrl', ['$scope', 'session', function($scope, session) {
/* 'session' here is the key we passed to resolve above.
* It will already be loaded and resolved before this function is called */
$scope.session = session;
}]);
Angular is hardcoded to make the requests async. To do it synchronously would take other code, whether custom or from some other library. Here is line 9269 from angular 1.0.7:
xhr.open(method, url, true);
The third param makes it asynchronous.
I would take a step back and think about how you are doing things. You could provide some loading indicator while your async request is going and easily control the loading of a view in the success callback so that it doesn't appear until the data is loaded.
A better solution is to add a response interceptor:
checkAuth = ($q, $location) ->
success = (response) ->
response
error = (response) ->
errorCode = response.status
$location.path '/login' if errorCode is 403 or errorCode is 401
# $q.reject response - no need because we are redirecting before any other promises in the chain will resolve (were breaking our future promises)
(promise) ->
promise.then success, error
$httpProvider.responseInterceptors.push checkAuth
And in your $routeProvider, or $stateProvider in my case:
.state 'user',
templateUrl: 'assets/views/user/partials/user.html'
resolve:
credentials: (checkLogIn) ->
checkLogIn.get().$promise
When checkLogIn.get()'s promise is rejected (the error handler is fired), assuming it's a 401 or 403 response (unauthenticated or unauthorized), the promise chain will be broken and the user will be "redirected" to /login.
With this method, any error calls will be channelled into the interceptor, instead of handling errors on a route-by-route basis.