Redirect to 404 if unauthorized angular - angularjs

I am using the rootscope event in run function to check for entitlements like so
$rootScope.$on('$routeChangeStart', function() {
$http.get('auth/urlentitlementcheck').then(function(response) {
if (!response) {
$state.go('app.unauth');
}
});
});
But the problem is my controller functions are still getting called before this redirect happens.Is there another way to check for entitlement so that is happens before anything is called or loaded or am i doing something wrong?

Related

How can i redirect to another page without loading the current page in angularjs

I want to redirect to another page without loading the currentpage.
let me explain my task. I am having user login condition.
If the user has not login and he tries to enter the URL directly EX("localhost/sample.html") means it will come to login page. For me this condition is working nicely.
First sample.html page open and then only it will redirect to login. The user able to see the Data in sample.html.
var logincheck = function () {
debugger;
$http.get('loggedin').success(function (user) {
alert(user);
// Authenticated
if (user != '0') {
refresh();
return;
}
// Not Authenticated
else {
$window.location.href = '/';
}
});
};
logincheck();
Is there any way to go login page without loading the sample.html page.
A interesting way do to this is by checking this condition in a $route event. For example, you can write this code in your app.run:
$rootScope.$on('$routeChangeStart', function(event, next, current) {
if(next.$$route && userNotAuthenticated) {
$location.href('/');
}
});
The advantage is that it will work for your whole app, you won't have to write code for each page.
In your main controller (the one attached to index) you should do a login check and set a variable to true/false. In your controller do a check on this variable, and do a $state.go() if user is not logged in.
Use ng-cloak directive in your index file where you attach your main controller like:
<body ng-controller="mainController" ng-cloak>
You can read up on ng-cloak here
This is how you can do it.
Whenever your angular app is boots, it runs the run function. So attach a location change listener in that function like below
App.run(['$rootScope', '$http', '$urlRouter', function($rootScope, $http, $urlRouter) {
$rootScope.$on('$locationChangeSuccess', function(event) {
event.preventDefault();// prevents the location change
$http({// make a http call to check if user is logged in or not.
url: '/path/to/check/auth',
method: 'post',
data: {}
}).then(function(response){
if(response.data.isUserLoggedIn){//If user is logged in.
$urlRouter.sync();
}else{//user is not logged in
window.location.assign('/login');
}
}, function() {
window.location.assign('/login');
});
});
$urlRouter.listen();
}]);

Pass authentication information from MVC into angular

My project uses MVC to deliver the initial markup of my site
The MVC controller is super simple:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
I have my ng-app tag, bundling, and #RenderBody in a layout view:
<!DOCTYPE html>
<html ng-app="myAppName">
<head>
#Styles.Render("~/Content/css")
</head>
<body>
<div class="container body-content">
#RenderBody()
</div>
#Scripts.Render("~/bundles/aBundle")
</body>
</html>
And my Index view is stripped down as simple as possible:
<ng-view></ng-view>
My angular app.ts file looks like this:
module app {
var main = angular.module("myAppName", ["ngRoute", "breeze.angular"]);
main.config(routeConfig);
routeConfig.$inject = ["$routeProvider"];
function routeConfig($routeProvider: ng.route.IRouteProvider): void {
$routeProvider
.when("/home",
{
templateUrl: "app/views/homeView.html",
controller: "HomeController as vm"
})
.when("/itemDetail/:itemId",
{
templateUrl: "app/views/itemDetailView.html",
controller: "ItemDetailController as vm"
})
.when("/addItem",
{
templateUrl: "app/views/addItemView.html",
controller: "AddItemController as vm"
})
.when("/login",
{
templateUrl: "app/views/loginView.html",
controller: "LoginController as vm"
})
.otherwise("/home");
}
}
I can inspect the Request sent by the user in the MVC controller, or in the Razor view using #Request.IsAuthenticated to see if the user is logged in, but what is the best way to pass this information to my angular app so that I can properly route the user to a login page when they first sign on, but skip the login page if they have an active session on the server?
The research I have done to try and figure this out has suggested to me that I probably need to create an angular service to store a boolean value regarding whether the user is authenticated or not. Then, I need to add some code to check this service for every route using $routeChangeStart, and redirecting to the login page only when necessary. I have looked at many examples, but can't quite put the pieces together in the context of my own application.
Could someone help me connect the dots, please?
I'm developing a project that I'm not using razor. Only html and angular in the views. So... What I did is:
I created a directive "appWrapper" that holds the layout. Inside of it there is a section that has the ng-view.
This directive uses a controller, the AuthCtrl, and this controller uses a service, the AuthService.
Because of this, I have access to everything that's inside this controller in the entire html. So I could say ng-click="vm.logout()" in an item in the sidebar, for example.
This "AuthService" has some methods that can be called by the controller. Login and logout being 2 of them.
When I execute login, I set some cookies with some information I get back from my controller.
When I logout, I remove these cookies.
On the app.js, where my routes are, I have I .run at the end that checks these cookies every time the location (url) is going to be changed. If they exist, it lets the user continue. If not, it redirects the user to the login.
Did it help? If needed, I can post the code for you to use as example.
No need to create an action inside a controller for checking this. It would require your application to go to the server, back to the browser, back to the server and back to the browser for every action the user takes. This is not good.
I can share what I do in my own apps, and although I am using ui-router, the same technique can easily be applied to the built in router.
Basic logical workflow:
Automatically add a resolve to every route that fetches the user's current auth status and caches it.
Check if route requires authentication, and reject if user is not logged in, otherwise allow to proceed normally.
On $routeChangeError (or something similar) check to see what action to perform.
The advantage of this is it allows you to have a great deal of flexibility with regard to providing a nice client side experience with security.
Adding Credentials to Every Route:
let originalStateFunction = $stateProvider.state;
$stateProvider.state = function (state, config) {
//
// The "allowAnonymous" is something we added manually
// This will become important later because we might have
// routes that don't require authentication, like say
// the login page
//
if (angular.isDefined(config) && !config.allowAnonymous) {
_.defaults(config.resolve || (config.resolve = {}), {
/*#ngInject*/
userSession: function userSessionSecurityCheck($q, sessionService) {
let def = $q.defer();
sessionService.getSession()
.then(session => {
if(!session.isAuthenticated){
def.reject({
error:'AUTHENTICATION_REQUIRED'
});
}
});
return def.promise;
//You could also do more complex handling here...
// like check permissions for specific routes
// and reject the promise if they fail.
}
});
}
//Now call the original state/when method with our
// newly augmented config object
return originalStateFunction.apply(this, arguments);
};
Inspecting The Route Error
.run($rootScope => {
"ngInject";
$rootScope.$on('$stateChangeError', (event, toState, toParams, fromState, fromParams, error) => {
event.preventDefault();
if(error.error === "AUTHENTICATION_REQUIRED"){
//Now you can redirect the user appropriately
}
});
});
You can create a simple endpoint to return the current user's status by inspecting the Principal, and so long as you are smart about caching that on the client, you will only incur the hit once per user.
The full sample code is too large for SO, but hopefully this gives you a good starting point.
I am thinking you would do it along the lines of below:
MVC controller:
[Authorize] // Make sure we're authorising the whole controller.
public class ProfileController : Controller
{
public ActionResult IsLoggedIn()
{
return Json(this.Request.IsAuthenticated);
// you may need allow get as the second parameter
}
}
Angular:
app.factory('ProfileService', function ($http) {
var service = {
isLoggedIn: isLoggedIn
};
return service;
function isLoggedIn() {
return $http.get('Profile/IsLoggedIn').then(function (response) {
return response.data; // this depends on what the response object looks like
});
}
});
app.run(function (ProfileService, $location) {
ProfileService.isLoggedIn().then(function (isLoggedIn) {
if (!isLoggedIn) {
$location.path('/unauthorised'); // just redirect them to the unauthorised route.
}
});
});
This means that everytime your app runs it will check to see whether you are logged in. You can also use this profile service to go grab other information about the user! And you can pull this service into any other module you wish to perform this kind stuff.
Remember that it is pointless trying to secure the javascript because it is run in a sandbox. But always make sure that you use [Authorize] attributes in your MVC code so that the server is always enforcing Authorisation and Authentication.
Before anyone says this isn't Typescript, any Javascript is also valid Typescript. I leave it to the user to put in the type defs.
-- More Info --
You can add a xmin cache expiry on the is logged in or store these details into local storage if you are constantly requesting the information:
app.factory('ProfileService', function ($http, $q) {
var service = {
isLoggedIn: isLoggedIn
};
var cacheExpiries = {
loggedIn: { value: null, expiry: null }
};
return service;
function isLoggedIn() {
var cacheObj = cacheExpiries['loggedIn'];
var useCache = false;
if (cacheObj.expiry) {
useCache = new Date() < cacheObj.expiry;
}
if (useCache) {
// because http returns a promise we need to
// short circuit function with a promise
return $q(function (res, rej) {
res(cacheObj.value);
});
}
// set the new expiry for the cache, this just adds 5 minutes to now
cacheObj.expiry = new Date(new Date().setMinutes(new Date().getMinutes() + 5));
return $http.get('Profile/IsLoggedIn').then(function (response) {
cacheObj.value = response.data;
return response.data; // this depends on what the response object looks like
});
}
});
You could easily encapsulate the cache into a factory with a proper key value storage api. This would extract some of the logic out of your thin service ( to keep it thin )
Local Storage and Cookie Storage
I have used this module for years to do local storage access, this can be slotted in place of the cache or you could still have your cache service to wrap this storage solution so that you can completely stay decoupled from your dependencies.

Resolve dependencies not available in Jasmine test

I am currently setting up a resolve property on one of my routes in my Angular app. I am using ngRoute. Also, I inject a service called Session into my resolve function.
My route looks like this:
$routeProvider.when('/projects', {
templateUrl: 'views/projects/index.html',
controller: 'ProjectsCtrl',
resolve: {
beforeAction: function (Session) { // <-- here's the injection
// this logs an object in the browser,
// but in my test it logs as undefined
console.log('Session', Session);
}
}
});
In my browser, this logs Session, Object {} to my console, as expected.
However, when I run my tests, the same line prints Session, undefined to my console.
My test looks like this:
beforeEach(module('visibilityApp'));
var route;
describe('/projects', function () {
beforeEach(inject(function ($route) {
route = $route;
}));
it('checks if the user is logged in', function () {
// Here I just invoke the function that's assigned to the
// route's resolve property, but Session then seems
// to be undefined.
route.routes['/projects'].resolve.beforeAction();
// then more of the test...
});
});
I've already found out that it doesn't really matter what I inject into the resolve function. If I inject $location and log that, it's the same spiel: it works in my browser, but is undefined when I run it as a test.
My tests on Jasmine and Karma. The app was generated with Yeoman.
Why are the resolve dependencies undefined in my test? Is there some additional setup my tests need?
I guess this was one of those "I need to step away from it for an hour and come back to it" situations. It turns out that I have to inject the Session service myself if I invoke the resolve function manually.
So instead of
route.routes['/projects'].resolve.beforeAction();
I need to pass in the Session
route.routes['/projects'].resolve.beforeAction(Session);
Otherwise, the Session parameter of obviously going to be undefined. To do that, I injected the Session service into my test as such:
beforeEach(module('visibilityApp'));
var route,
Session;
describe('/projects', function () {
beforeEach(inject(function ($route, _Session_) {
route = $route;
Session = _Session_;
}));
it('checks if the user is logged in', function () {
route.routes['/projects'].resolve.beforeAction(Session);
// then more of the test...
});
});

ui-router StateChange event go through even with redirection

I am developping an OAuth Provider application, using AngularJS and ui-router.
For each state change, I do the following check:
If the user is already logged in:
1.1 If the user is not an admin -> redirect him to the callBackUrl
1.2 If the user is an admin, do nothing
If the user is not logged in:
2.1 If the user tries to access an admin page -> redirect him back to login
2.2 If not, do nothing
my ui-router run method is the following:
.run(function ($rootScope, $state, $auth, accountService, $window, $stateParams) {
$rootScope.$on('$stateChangeStart', function (e, toState, toParams, fromState, fromParams) {
accountService.getUser({ token: $auth.getToken() }).$promise
.then(function (response) {
if (response.isAdmin === false) {
e.preventDefault();
$window.location.href = $stateParams.callBackUrl;
return;
}
})
.catch(function (response) {
if (toState.name.split(".")[0] === 'admin') {
e.preventDefault();
$state.go('root.login');
}
});
});
});
Everything is OK except for the part where I redirectthe user to the callback URL using $window
$window.location.href = $stateParams.callBackUrl;
This redirection takes 2-3 seconds, and in the meantime my user can see the page he is trying to access on my application. I thought the use of preventDefault() would solve that but it doesn't. Do you know how I could hold the $statechangeevent so that the user is redirected directly to the callback URL?
Thanks
I would put it this way:
The above approach allows user everything, until he is not really proven to be UN-authenticated. Why that? Because the code is calling service and evaluating all the stuff once the data are received. Meanwhile - we trust the user.
So, I'd suggest to change the approach
NEVER trust the user. He/she must to do the best to prove he/she is the right one, to get somewhere ... (well, kind of that...)
I described one possible way (with working example) here:
Confusing $locationChangeSuccess and $stateChangeStart
Just a piece of code to cite
Th first part of the $rootScope.$on('$stateChangeStart', ...:
// if already authenticated...
var isAuthenticated = userService.isAuthenticated();
// any public action is allowed
var isPublicAction = angular.isObject(toState.data)
&& toState.data.isPublic === true;
// here - user has already proved that he is the one
// or the target is public (e.g. login page)
// let him go, get out of this check
if (isPublicAction || isAuthenticated) {
return;
}
The second part, user is not trusted, he requires access to private stuff
// now - stop everything
// NO navigation
// we have to be sure who user is, to continue
// stop state change
event.preventDefault();
// async load user
userService
.getAuthObject()
.then(function (user) {
var isAuthenticated = user.isAuthenticated === true;
if (isAuthenticated) {
// let's continue, use is allowed
$state.go(toState, toParams)
return;
}
// log on / sign in...
$state.go("login");
})
Check that, in action, here
I know this was asked a while ago, but here is a possible solution:
Ui-router actually provides a fantastic way to solve this. By using a "resolve" within your $stateProvider, you can check that the user is authenticated before the controller of that particular state is even instantiated. Here is what ui-router docs say about resolve:
Resolve
You can use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.
If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $stateChangeSuccess event is fired.
https://github.com/angular-ui/ui-router/wiki - Section for resolve is almost half way down page
You can run accountService.getUser within the resolve to check for an authenticated user, and by doing so, would prevent someone who is not authed from seeing the view they are trying to route to.
The resolve is set up inside the $stateProvider, and may look something like this:
$stateProvider.state('myState', {
resolve: {
userAuth: function(accountService) {
return accountService.getUser();
}
}
)
If you notice in the above example, I set a property called userAuth within the resolve. This can now be injected into any controller or service and then you can check against it for authenticated users. Each state that needs to be a "protected" view can contain the resolve, and then the 2-3 second flash of the view won't occur, as the controller hasn't been instantiated, and the user is redirected to another state.

How do I check for login or other status before launching a route in Angular with routeProvider?

Let's say I have 4 routes - 2 require the user to be logged in, 2 do not. My app init looks like:
$routeProvider.when('/open1',{templateUrl:'/open1.html',controller:'Open1'});
$routeProvider.when('/open2',{templateUrl:'/open2.html',controller:'Open2'});
$routeProvider.when('/secure1',{templateUrl:'/secure1.html',controller:'Secure1'});
$routeProvider.when('/secure2',{templateUrl:'/secure2.html',controller:'Secure2'});
Routes /open1 and /open2 are open to all, while routes /secure1 and /secure2 require the user to be logged in and, if not, take some action, e.g. redirect to login or launch a warning. I can determine the user's state by using my Auth service and calling, e.g., Auth.isLogin(). So the result would be:
going to /open1 and /open2 always go to the template and controller declared above
if Auth.isLogin() returns true, /secure1 and /secure2 go to the above-declared template and controller
if Auth.isLogin() returns false, /secure1 and /secure2 take some other action, e.g. $location.path('/login')
I could put logic in the Secure1 and Secure2 controllers that checks, but that is repetitive and mixes up responsibilities, makes them harder to test, etc.
Is there a way that I can use the $routeProvider to declare, "check this route and this route and if not, redirect"? I was thinking of using resolve somehow, but not quite sure how to work it in (docs on resolve are not very clear, and few helpful examples).
EDIT:
based on the answers below, it appears there are three philosophies for doing this:
Using resolve to check logged in and fail the promise, and then catching the $routeChangeError event to redirect http://www.sitepoint.com/implementing-authentication-angular-applications/
Using just $routeChangeStart event to check logged in and redirect http://arthur.gonigberg.com/2013/06/29/angularjs-role-based-auth/
Using just resolve to check logged in and redirect http://midgetontoes.com/blog/2014/08/31/angularjs-check-user-login
The 2nd option is what the two answerers have suggested.
As in my comments above, there are 3 different paths (plus the ability to use a directive if you want to control it from within html templates). I ended up following
https://midgetontoes.com/angularjs-check-user-login/
which essentially is as follows:
$routeProvider.when('/secure', {
templateUrl: '/secure.html',
controller: 'Secure',
resolve:{
loggedIn:onlyLoggedIn
}
});
And then onlyLoggedIn:
var onlyLoggedIn = function ($location,$q,Auth) {
var deferred = $q.defer();
if (Auth.isLogin()) {
deferred.resolve();
} else {
deferred.reject();
$location.url('/login');
}
return deferred.promise;
};
Simple, works like a charm. If I ever need a directive, I will pull this piece into a service.
This blog post deals with user authentication in AngularJS using directives.
The $route service emits $routeChangeStart before a route change.
If you don't use directives, you can catch that event by calling app.run (you can place it after the code where you define the routes [app.config]). For example:
For full disclosure I use ui.router and this is an adapted code from $stateChangeStart I use in my app
var app = angular.module('app');
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/open1',{templateUrl:'/open1.html',controller:'Open1'});
$routeProvider.when('/open2',{templateUrl:'/open2.html',controller:'Open2'});
$routeProvider.when('/secure1',{templateUrl:'/secure1.html',controller:'Secure1'});
$routeProvider.when('/secure2',{templateUrl:'/secure2.html',controller:'Secure2'});
}]);
app.run(['$rootScope', '$location', 'Auth', function($rootScope, $location, Auth) {
$rootScope.$on('$routeChangeStart', function(event, currRoute, prevRoute){
var logged = Auth.isLogin();
//check if the user is going to the login page
// i use ui.route so not exactly sure about this one but you get the picture
var appTo = currRoute.path.indexOf('/secure') !== -1;
if(appTo && !logged) {
event.preventDefault();
$location.path('/login');
}
});
}]);
I had the same problem and I did it this way:
var app = angular.module('myModule',["ui-bootstrap"]);
And then listen for a locationchange in the app (this will also trigger onEnter of a page)
app.run(function ($rootScope, $location, $cookieStore) {
$rootScope.$on("$locationChangeStart", function (event, next, current) {
//Here you can check whatever you want (servercall, cookie...)
});
}
I Hope this helps!

Resources