I have a basic authentication check on route change which simply checks the existence of a SessionStorage key/val. However, I have noticed that if an unauthenticated user navigates to a forbidden page who's controller contains an AJAX call, the AJAX call still happens in the background even though the forbidden page doesn't load and the user is instead redirected to login. To get around this, I have added the same authentication check in every controller. This is becoming a bit tedious and I was wondering if there is a better way, or a way I can globally check authentication before a controller's methods are run. Here is the auth function on route change:
app.run(function ($rootScope, $location, authenticationService) {
$rootScope.clearAlerts = function(){
$rootScope.alerts = []
}
$rootScope.credentials = {
username: "",
password: ""
};
$rootScope.goto = function(url){
$location.path(url);
}
$rootScope.authenticated = authenticationService.isLoggedIn()
$rootScope.logOut = function () {
authenticationService.logOut();
}
var publicRoutes = ['/login'];
$rootScope.$on('$routeChangeStart', function (event, next, current) {
$rootScope.credentials.from = $rootScope.desiredPath || '/login'
$rootScope.authenticated = authenticationService.isLoggedIn();
if (!_(publicRoutes).contains($location.path()) && !$rootScope.authenticated) {
$rootScope.desiredPath = $location.path();
$location.path('/login');
}
})
})
I suggest you should check authentication on server side and return appropriate message on client side
Below is the url help
http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application
and Write global handler in client side to check it
Below is a sample code:
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives'], function ($routeProvider, $locationProvider, $httpProvider) {
var interceptor = ['$rootScope', '$q', function (scope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
window.location = "./index.html";
return;
}
// otherwise
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
Related
We have an application that requires users to be logged in. Once logged in the user interacts with web api. Our issue is when the authentication expires we want to show a Login page, for the user to log back in without being redirected. This way they will not lose there work. Our issue is as follows:
Inside our Controller an request is made
$http.get("/api/Document/GetDocumentsManage").success(function (response) {
$scope.Documents = response;
});
On the server we identify that the user is no longer authenticated and we reject the call. We then use an interceptor to catch the error and handle it to show a popup modal.
$httpProvider.interceptors.push(function ($q, $injector) {
return {
'responseError': function (rejection) {
var response = rejection;
var defer = $q.defer();
if (rejection.status == 401 || rejection.status == 400) {
var modal = $injector.get("$mdDialog");
modal.show({
parent: angular.element("body"),
targetEvent: window.event,
templateUrl: "/directives/LoginPage/Login.html",
controller: "loginController",
onRemoving: function () {
var $http = $injector.get("$http");
$http(rejection.config);
}
});
}
}
};
});
With this code we can successfully re-authenticate without the user navigating away from the page, and then once authenticated again we can execute the original request. Our issue is the resubmitted request is not bound to the original .success callback of our request. Therefore in this example $scope.Documents does not get set to the response. Is there anyway we can rerun any request that failed and continue execution?
You are definitely on the right track! You just need a few minor changes to ensure that the result makes in back to your controller:
$httpProvider.interceptors.push(function ($q, $injector) {
return {
'responseError': function (rejection) {
var response = rejection;
var defer = $q.defer();
var $http = $injector.get("$http"); //moved this for readability
var modal = $injector.get("$mdDialog"); //moved this for readability
if (rejection.status == 401 || rejection.status == 400) {
modal.show({
parent: angular.element("body"),
targetEvent: window.event,
templateUrl: "/directives/LoginPage/Login.html",
controller: "loginController",
onRemoving: function () {
// resolve the deferred
deferred.resolve();
}
});
// return the promise object
return deferred.promise.then(function() {
// return the new request promise
return $http(rejection.config);
});
}
return $q.reject(rejection);
}
};
});
Take a look at the is blog example where they are doing the same thing you are trying to do: http://www.webdeveasy.com/interceptors-in-angularjs-and-useful-examples/#sessionrecovererresponseerrorinterceptor
I have a bit trouble finding out how i can managed my authentication roles on the front end. I want to access the authentication role and pass it to the root scope but it returns an array instead.
I want to know which role the user has add some privilege to each roles.
How could this be done ?
If i call user /user
{"details":{"remoteAddress":"0:0:0:0:0:0:0:1","sessionId":"1F64CC142A715A53FFF4A9329E6B933D"},"authorities":[{"authority":"USER_ROLE"}],"authenticated":true,"principal":{"password":null,"username":"1#1","authorities":[{"authority":"USER_ROLE"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,"enabled":true},"credentials":null,"name":"1#1"}
This is my login controller :
(function () {
'use strict';
angular
.module('app')
.controller('LoginController', LoginController);
LoginController.$inject = ['$rootScope', '$scope', '$http', '$location', '$route','$localStorage'];
function LoginController($rootScope, $scope, $http, $location, $route,$localStorage) {
$scope.tab = function(route) {
return $route.current && route === $route.current.controller;
};
var authenticate = function(credentials, callback) {
var headers = credentials ? {
authorization : "Basic "
+ btoa(credentials.username + ":"
+ credentials.password)
} : {};
$http.get('user/', {
headers : headers
}).success(function(data) {
if (data.name) {
//This returns an array...
console.log(data.authorities);
console.log("Good Credentials");
$rootScope.authenticated = true;
} else {
$rootScope.authenticated = false;
}
callback && callback($rootScope.authenticated);
}).error(function() {
$rootScope.authenticated = false;
callback && callback(false);
});
};
authenticate();
$scope.credentials = {};
$scope.login = function() {
authenticate($scope.credentials, function(authenticated) {
if (authenticated) {
console.log("Login succeeded");
console.log($scope.credentials.username);
$location.path("/app/dashboard");
$scope.error = false;
$rootScope.authenticated = true;
//Here we add all the variables we need to the session that are safe to be seen
//In the future we can replace this and load this in the splash screen every time the application loads so we are sure the
// variables are stored
} else {
console.log("Login failed")
$location.path("/login");
$scope.error = true;
$rootScope.authenticated = false;
}
})
};
//Logout
$scope.logout = function() {
$http.post('logout', {}).success(function() {
$rootScope.authenticated = false;
$location.path("/");
}).error(function(data) {
$location.path("/");
console.log("Logout failed")
$rootScope.authenticated = false;
});
}
}
})();
This is my controller
#RequestMapping("/user")
public Principal user(#AuthenticationPrincipal Principal user) {
return user;
}
As described in the Spring documentation, #AuthenticationPrincipal will return value to the Authentication.getPrincipal(). However, if you look at Authentication, you will see that the authorities of the user are accessed separately from the principal, using Authentication.getAuthorities().
You can do a couple of things, either modify your custom Principal to also include the roles of the users, or you can also grab the authorities and serialize them in addition to the principal within your controller.
You can get the current authentication via: SecurityContextHolder.getContext().getAuthentication();
I'm trying to get a value from a URL part, into my $http getURL request. I have tried a few solutions (such as HTML5mode) but have not had success.
Here is my code:
angular.module('myapp123.products', [])
.factory('productsApi', ['$http', '$location',
function($http, $location){
var BASE_URL = 'http://stashdapp-t51va1o0.cloudapp.net/api/item/';
return {
get: getApiData
};
function getData() {
var product_id = $location.path().split("/")[3] || "Unknown"; //URL = /#/product/id/1234 <---
return $http.get(BASE_URL + product_id);
}
}]
)
.controller('productsCtrl', ['$scope', '$log', 'productsApi', 'UserService',
function($scope, $log, productsApi, UserService) {
$scope.isVisible = function(name){
return true;// return false to hide this artist's albums
};
// <====== Rewrite with accounts preferences
productsApi.getApiData()
.then(function (result) {
//console.log(JSON.stringify(result.data)) //Shows log of API incoming
$scope.products = result.data;
})
.catch(function (err) {
$log.error(err);
});
}
]);
The code in your example has a lot of syntax errors in it. Here is what it should look like, based on what I think you are going for...
angular.module('myapp123.products', [])
.config(locationConfig)
.factory('productsApi', productsApiFactory)
;
locationConfig.$inject = ['$locationProvider'];
function locationConfig($locationProvider) {
$locationProvider.html5Mode(true);
}
productsApiFactory.$inject = ['$http', '$location'];
function productsApiFactory($http, $location) {
var BASE_URL = 'http://stashdapp-t51va1o0.cloudapp.net/api/list/';
return {
get: getData
};
function getData() {
var product_id = $location.path().split("/")[3] || "Unknown";
return $http.get(BASE_URL + product_id);
}
}
In this version, the config function is correctly defined to set up html5mode and the service factory is configured to use $location each time the get() method is called.
You would use the service in a controller like this:
ExampleController.$inject = ['productsApi'];
function ExampleController(productsApi) {
productsApi.get()
.then(function onSuccess(res) {
// handle successful API call
})
.catch(function onError(err) {
// handle failed API call
})
;
}
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 am looking for a way to do these two things, first I want to redirect the user to a login page if no SessionID is found and second I would like to hear your opinion about persisting session ID in memory only (no cookies).
The solution I came up with for the redirect is:
1 - Create a service called OAuth that will check if SessionID exists and if not, redirects to login page, the service is also responsible for the login and logout methods.
app.factory('OAuth', ['$http', function ($http) {
var _SessionID = '';
return {
login: function () {
//Do login ans store sessionID in var _SessionID
},
logout: function () {
//Do logout
},
isLoggedIn: function () {
if(_SessionID) {
return true;
}
//redirect to login page if false
}
};
}]);
2 - Inject the new OAuth service in each controller and check if user isLoggedIn
app.controller('myCtrl', ['$scope', 'OAuth', function ($scope, OAuth) {
//check if user is logged
OAuth.isLoggedIn();
}]);
Questions:
1 - The isLoggedIn() method will be called in all controllers, so I wonder if there is a way to do this without having to inject the service and call it in each controller.
2 - Instead of having a cookie to store the sessionID I want to save it in OAuth's _SessionID variable and for each request send it to the server. Is this a viable/secure approach? Can you give me some ideas for that?
Thanks!
I use a similar strategy (intercepting 401 responses from the server). You can check out the full example here : https://github.com/Khelldar/Angular-Express-Train-Seed
It uses node and mobgodb on the backend for session store and a trimmed down http interceptor on the client that doens't retry requests like the one Dan linked above:
var interceptor = ['$q', '$location', '$rootScope', function ($q, $location, $rootScope) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
if (status == 401) {
$location.path('/login');
}
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);
I would start here, Witold has created this cool interceptor that works off of http responses. I use it and its been really helpful.
In my case, I used
interceptor with $httpProvider
config
and $window dependency, as $location just appended the path to the existing url. What happened was like "http://www.tnote.me/#/api/auth", and it should have bene like "http://www.tnote.me/auth"
The code snippet is like this.
noteApp = angular.module('noteApp', ['ngRoute', 'ngCookies'])
.factory('authInterceptor', ['$rootScope', '$q', '$cookies', '$window',
function($rootScope, $q, $cookies, $window) {
return {
request: function (req) {
req.headers = req.headers || {};
if ($cookies.token) {
req.headers.Authorization = 'Bearer ' + $cookies.token;
}
return req;
},
responseError: function (rejection) {
if (rejection.status == 401) {
$window.location = '/auth';
}
return $q.reject(rejection);
}
}
}])
.config(['$routeProvider', '$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('authInterceptor');
}
])
this will work. It works fine in my application
var interceptor = function ($q, $location) {
return {
request: function (config) {//req
console.log(config);
return config;
},
response: function (result) {//res
console.log('Repos:');
console.log(result.status);
return result;
},
responseError: function (rejection) {//error
console.log('Failed with', rejection.status, 'status');
if (rejection.status == 403) {
$location.url('/dashboard');
}
return $q.reject(rejection);
}
}
};
module.config(function ($httpProvider) {
$httpProvider.interceptors.push(interceptor);
});