AngularJs Authentication how to avoid flickering - angularjs

I am building an application with AngularJs and I want to prevent user from accessing application's internal pages if he is not logged in.
I have a separate application for login (loginApp) and for the main application (mainApp). What I have done so far in mainApp in order to support authentication is the following:
mainApp.config(['$routeProvider', function($routeProvider) {
$routeProvider. when('/apage', {
templateUrl: ...,
controller: ....,
resolve: {
load: function($q, AuthService){
var defer = $q.defer();
if(AuthService.notLoggedIn) {
defer.reject('user_not_logged_in');
} else {
defer.resolve();
}
return defer.promise;
}
}
}).run(function($rootScope, $window) {
$rootScope.$on('$routeChangeError', function(event, next, current, rejection) {
if (rejection === 'user_not_logged_in') {
$window.location = '/html/pages/login.html';
}
});
});
}])
My mainApp's html page is consisted of a navbar (which is controlled by controllers defined in navbarControllers module) and a div enclosing ng-view directive (partial pages go there).
My problem is that the code above works fine for the partial pages but not for the navbar. Navbar is loaded normally, its controllers try to make calls to backend but they fail (as user is not authenticated) so alerts popup and then redirects to login page. Why is this happening? How can I prevent navbarControllers module from loading and navbar from showing?

You can use something like this
#if (Request.IsAuthenticated) {
// code for your navbar which required user authentication
}
else {
// navbar for logged out user.
}

Related

How to wait for authentication check before loading default state in angularjs applicaton with ui-router [duplicate]

I am new to AngularJS, and I am a little confused of how I can use angular-"ui-router" in the following scenario:
I am building a web application which consists of two sections. The first section is the homepage with its login and signup views, and the second section is the dashboard (after successful login).
I have created an index.html for the home section with its angular app and ui-router config to handle /login and /signup views,
and there is another file dashboard.html for the dashboard section with its app and ui-router config to handle many sub views.
Now I finished the dashboard section and don't know how to combine the two sections with their different angular apps. How could I tell the home app to redirect to the dashboard app?
I'm in the process of making a nicer demo as well as cleaning up some of these services into a usable module, but here's what I've come up with. This is a complex process to work around some caveats, so hang in there. You'll need to break this down into several pieces.
Take a look at this plunk.
First, you need a service to store the user's identity. I call this principal. It can be checked to see if the user is logged in, and upon request, it can resolve an object that represents the essential information about the user's identity. This can be whatever you need, but the essentials would be a display name, a username, possibly an email, and the roles a user belongs to (if this applies to your app). Principal also has methods to do role checks.
.factory('principal', ['$q', '$http', '$timeout',
function($q, $http, $timeout) {
var _identity = undefined,
_authenticated = false;
return {
isIdentityResolved: function() {
return angular.isDefined(_identity);
},
isAuthenticated: function() {
return _authenticated;
},
isInRole: function(role) {
if (!_authenticated || !_identity.roles) return false;
return _identity.roles.indexOf(role) != -1;
},
isInAnyRole: function(roles) {
if (!_authenticated || !_identity.roles) return false;
for (var i = 0; i < roles.length; i++) {
if (this.isInRole(roles[i])) return true;
}
return false;
},
authenticate: function(identity) {
_identity = identity;
_authenticated = identity != null;
},
identity: function(force) {
var deferred = $q.defer();
if (force === true) _identity = undefined;
// check and see if we have retrieved the
// identity data from the server. if we have,
// reuse it by immediately resolving
if (angular.isDefined(_identity)) {
deferred.resolve(_identity);
return deferred.promise;
}
// otherwise, retrieve the identity data from the
// server, update the identity object, and then
// resolve.
// $http.get('/svc/account/identity',
// { ignoreErrors: true })
// .success(function(data) {
// _identity = data;
// _authenticated = true;
// deferred.resolve(_identity);
// })
// .error(function () {
// _identity = null;
// _authenticated = false;
// deferred.resolve(_identity);
// });
// for the sake of the demo, fake the lookup
// by using a timeout to create a valid
// fake identity. in reality, you'll want
// something more like the $http request
// commented out above. in this example, we fake
// looking up to find the user is
// not logged in
var self = this;
$timeout(function() {
self.authenticate(null);
deferred.resolve(_identity);
}, 1000);
return deferred.promise;
}
};
}
])
Second, you need a service that checks the state the user wants to go to, makes sure they're logged in (if necessary; not necessary for signin, password reset, etc.), and then does a role check (if your app needs this). If they are not authenticated, send them to the sign-in page. If they are authenticated, but fail a role check, send them to an access denied page. I call this service authorization.
.factory('authorization', ['$rootScope', '$state', 'principal',
function($rootScope, $state, principal) {
return {
authorize: function() {
return principal.identity()
.then(function() {
var isAuthenticated = principal.isAuthenticated();
if ($rootScope.toState.data.roles
&& $rootScope.toState
.data.roles.length > 0
&& !principal.isInAnyRole(
$rootScope.toState.data.roles))
{
if (isAuthenticated) {
// user is signed in but not
// authorized for desired state
$state.go('accessdenied');
} else {
// user is not authenticated. Stow
// the state they wanted before you
// send them to the sign-in state, so
// you can return them when you're done
$rootScope.returnToState
= $rootScope.toState;
$rootScope.returnToStateParams
= $rootScope.toStateParams;
// now, send them to the signin state
// so they can log in
$state.go('signin');
}
}
});
}
};
}
])
Now all you need to do is listen in on ui-router's $stateChangeStart. This gives you a chance to examine the current state, the state they want to go to, and insert your authorization check. If it fails, you can cancel the route transition, or change to a different route.
.run(['$rootScope', '$state', '$stateParams',
'authorization', 'principal',
function($rootScope, $state, $stateParams,
authorization, principal)
{
$rootScope.$on('$stateChangeStart',
function(event, toState, toStateParams)
{
// track the state the user wants to go to;
// authorization service needs this
$rootScope.toState = toState;
$rootScope.toStateParams = toStateParams;
// if the principal is resolved, do an
// authorization check immediately. otherwise,
// it'll be done when the state it resolved.
if (principal.isIdentityResolved())
authorization.authorize();
});
}
]);
The tricky part about tracking a user's identity is looking it up if you've already authenticated (say, you're visiting the page after a previous session, and saved an auth token in a cookie, or maybe you hard refreshed a page, or dropped onto a URL from a link). Because of the way ui-router works, you need to do your identity resolve once, before your auth checks. You can do this using the resolve option in your state config. I have one parent state for the site that all states inherit from, which forces the principal to be resolved before anything else happens.
$stateProvider.state('site', {
'abstract': true,
resolve: {
authorize: ['authorization',
function(authorization) {
return authorization.authorize();
}
]
},
template: '<div ui-view />'
})
There's another problem here... resolve only gets called once. Once your promise for identity lookup completes, it won't run the resolve delegate again. So we have to do your auth checks in two places: once pursuant to your identity promise resolving in resolve, which covers the first time your app loads, and once in $stateChangeStart if the resolution has been done, which covers any time you navigate around states.
OK, so what have we done so far?
We check to see when the app loads if the user is logged in.
We track info about the logged in user.
We redirect them to sign in state for states that require the user to be logged in.
We redirect them to an access denied state if they do not have authorization to access it.
We have a mechanism to redirect users back to the original state they requested, if we needed them to log in.
We can sign a user out (needs to be wired up in concert with any client or server code that manages your auth ticket).
We don't need to send users back to the sign-in page every time they reload their browser or drop on a link.
Where do we go from here? Well, you can organize your states into regions that require sign in. You can require authenticated/authorized users by adding data with roles to these states (or a parent of them, if you want to use inheritance). Here, we restrict a resource to Admins:
.state('restricted', {
parent: 'site',
url: '/restricted',
data: {
roles: ['Admin']
},
views: {
'content#': {
templateUrl: 'restricted.html'
}
}
})
Now you can control state-by-state what users can access a route. Any other concerns? Maybe varying only part of a view based on whether or not they are logged in? No problem. Use the principal.isAuthenticated() or even principal.isInRole() with any of the numerous ways you can conditionally display a template or an element.
First, inject principal into a controller or whatever, and stick it to the scope so you can use it easily in your view:
.scope('HomeCtrl', ['$scope', 'principal',
function($scope, principal)
{
$scope.principal = principal;
});
Show or hide an element:
<div ng-show="principal.isAuthenticated()">
I'm logged in
</div>
<div ng-hide="principal.isAuthenticated()">
I'm not logged in
</div>
Etc., so on, so forth. Anyways, in your example app, you would have a state for home page that would let unauthenticated users drop by. They could have links to the sign-in or sign-up states, or have those forms built into that page. Whatever suits you.
The dashboard pages could all inherit from a state that requires the users to be logged in and, say, be a User role member. All the authorization stuff we've discussed would flow from there.
The solutions posted so far are needlessly complicated, in my opinion. There's a simpler way. The documentation of ui-router says listen to $locationChangeSuccess and use $urlRouter.sync() to check a state transition, halt it, or resume it. But even that actually doesn't work.
However, here are two simple alternatives. Pick one:
Solution 1: listening on $locationChangeSuccess
You can listen to $locationChangeSuccess and you can perform some logic, even asynchronous logic there. Based on that logic, you can let the function return undefined, which will cause the state transition to continue as normal, or you can do $state.go('logInPage'), if the user needs to be authenticated. Here's an example:
angular.module('App', ['ui.router'])
// In the run phase of your Angular application
.run(function($rootScope, user, $state) {
// Listen to '$locationChangeSuccess', not '$stateChangeStart'
$rootScope.$on('$locationChangeSuccess', function() {
user
.logIn()
.catch(function() {
// log-in promise failed. Redirect to log-in page.
$state.go('logInPage')
})
})
})
Keep in mind that this doesn't actually prevent the target state from loading, but it does redirect to the log-in page if the user is unauthorized. That's okay since real protection is on the server, anyway.
Solution 2: using state resolve
In this solution, you use ui-router resolve feature.
You basically reject the promise in resolve if the user is not authenticated and then redirect them to the log-in page.
Here's how it goes:
angular.module('App', ['ui.router'])
.config(
function($stateProvider) {
$stateProvider
.state('logInPage', {
url: '/logInPage',
templateUrl: 'sections/logInPage.html',
controller: 'logInPageCtrl',
})
.state('myProtectedContent', {
url: '/myProtectedContent',
templateUrl: 'sections/myProtectedContent.html',
controller: 'myProtectedContentCtrl',
resolve: { authenticate: authenticate }
})
.state('alsoProtectedContent', {
url: '/alsoProtectedContent',
templateUrl: 'sections/alsoProtectedContent.html',
controller: 'alsoProtectedContentCtrl',
resolve: { authenticate: authenticate }
})
function authenticate($q, user, $state, $timeout) {
if (user.isAuthenticated()) {
// Resolve the promise successfully
return $q.when()
} else {
// The next bit of code is asynchronously tricky.
$timeout(function() {
// This code runs after the authentication promise has been rejected.
// Go to the log-in page
$state.go('logInPage')
})
// Reject the authentication promise to prevent the state from loading
return $q.reject()
}
}
}
)
Unlike the first solution, this solution actually prevents the target state from loading.
The easiest solution is to use $stateChangeStart and event.preventDefault() to cancel the state change when the user is not authenticated and redirect him to the auth state that is the login page.
angular
.module('myApp', [
'ui.router',
])
.run(['$rootScope', 'User', '$state',
function ($rootScope, User, $state) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
if (toState.name !== 'auth' && !User.authenticaded()) {
event.preventDefault();
$state.go('auth');
}
});
}]
);
I think you need a service that handle the authentication process (and its storage).
In this service you'll need some basic methods :
isAuthenticated()
login()
logout()
etc ...
This service should be injected in your controllers of each module :
In your dashboard section, use this service to check if user is authenticated (service.isAuthenticated() method) . if not, redirect to /login
In your login section, just use the form data to authenticate the user through your service.login() method
A good and robust example for this behavior is the project angular-app and specifically its security module which is based over the awesome HTTP Auth Interceptor Module
Hope this helps
I Created this module to help make this process piece of cake
You can do things like:
$routeProvider
.state('secret',
{
...
permissions: {
only: ['admin', 'god']
}
});
Or also
$routeProvider
.state('userpanel',
{
...
permissions: {
except: ['not-logged-in']
}
});
It's brand new but worth checking out!
https://github.com/Narzerus/angular-permission
I wanted to share another solution working with the ui router 1.0.0.X
As you may know, stateChangeStart and stateChangeSuccess are now deprecated. https://github.com/angular-ui/ui-router/issues/2655
Instead you should use $transitions http://angular-ui.github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html
This is how I achieved it:
First I have and AuthService with some useful functions
angular.module('myApp')
.factory('AuthService',
['$http', '$cookies', '$rootScope',
function ($http, $cookies, $rootScope) {
var service = {};
// Authenticates throug a rest service
service.authenticate = function (username, password, callback) {
$http.post('api/login', {username: username, password: password})
.success(function (response) {
callback(response);
});
};
// Creates a cookie and set the Authorization header
service.setCredentials = function (response) {
$rootScope.globals = response.token;
$http.defaults.headers.common['Authorization'] = 'Bearer ' + response.token;
$cookies.put('globals', $rootScope.globals);
};
// Checks if it's authenticated
service.isAuthenticated = function() {
return !($cookies.get('globals') === undefined);
};
// Clear credentials when logout
service.clearCredentials = function () {
$rootScope.globals = undefined;
$cookies.remove('globals');
$http.defaults.headers.common.Authorization = 'Bearer ';
};
return service;
}]);
Then I have this configuration:
angular.module('myApp', [
'ui.router',
'ngCookies'
])
.config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/resumen');
$stateProvider
.state("dashboard", {
url: "/dashboard",
templateUrl: "partials/dashboard.html",
controller: "dashCtrl",
data: {
authRequired: true
}
})
.state("login", {
url: "/login",
templateUrl: "partials/login.html",
controller: "loginController"
})
}])
.run(['$rootScope', '$transitions', '$state', '$cookies', '$http', 'AuthService',
function ($rootScope, $transitions, $state, $cookies, $http, AuthService) {
// keep user logged in after page refresh
$rootScope.globals = $cookies.get('globals') || {};
$http.defaults.headers.common['Authorization'] = 'Bearer ' + $rootScope.globals;
$transitions.onStart({
to: function (state) {
return state.data != null && state.data.authRequired === true;
}
}, function () {
if (!AuthService.isAuthenticated()) {
return $state.target("login");
}
});
}]);
You can see that I use
data: {
authRequired: true
}
to mark the state only accessible if is authenticated.
then, on the .run I use the transitions to check the autheticated state
$transitions.onStart({
to: function (state) {
return state.data != null && state.data.authRequired === true;
}
}, function () {
if (!AuthService.isAuthenticated()) {
return $state.target("login");
}
});
I build this example using some code found on the $transitions documentation. I'm pretty new with the ui router but it works.
Hope it can helps anyone.
Here is how we got out of the infinite routing loop and still used $state.go instead of $location.path
if('401' !== toState.name) {
if (principal.isIdentityResolved()) authorization.authorize();
}
I have another solution: that solution works perfectly when you have only content you want to show when you are logged in. Define a rule where you checking if you are logged in and its not path of whitelist routes.
$urlRouterProvider.rule(function ($injector, $location) {
var UserService = $injector.get('UserService');
var path = $location.path(), normalized = path.toLowerCase();
if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
$location.path('/login/signin');
}
});
In my example i ask if i am not logged in and the current route i want to route is not part of `/login', because my whitelist routes are the following
/login/signup // registering new user
/login/signin // login to app
so i have instant access to this two routes and every other route will be checked if you are online.
Here is my whole routing file for the login module
export default (
$stateProvider,
$locationProvider,
$urlRouterProvider
) => {
$stateProvider.state('login', {
parent: 'app',
url: '/login',
abstract: true,
template: '<ui-view></ui-view>'
})
$stateProvider.state('signin', {
parent: 'login',
url: '/signin',
template: '<login-signin-directive></login-signin-directive>'
});
$stateProvider.state('lock', {
parent: 'login',
url: '/lock',
template: '<login-lock-directive></login-lock-directive>'
});
$stateProvider.state('signup', {
parent: 'login',
url: '/signup',
template: '<login-signup-directive></login-signup-directive>'
});
$urlRouterProvider.rule(function ($injector, $location) {
var UserService = $injector.get('UserService');
var path = $location.path();
if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
$location.path('/login/signin');
}
});
$urlRouterProvider.otherwise('/error/not-found');
}
() => { /* code */ } is ES6 syntax, use instead function() { /* code */ }
Use $http Interceptor
By using an $http interceptor you can send headers to Back-end or the other way around and do your checks that way.
Great article on $http interceptors
Example:
$httpProvider.interceptors.push(function ($q) {
return {
'response': function (response) {
// TODO Create check for user authentication. With every request send "headers" or do some other check
return response;
},
'responseError': function (reject) {
// Forbidden
if(reject.status == 403) {
console.log('This page is forbidden.');
window.location = '/';
// Unauthorized
} else if(reject.status == 401) {
console.log("You're not authorized to view this page.");
window.location = '/';
}
return $q.reject(reject);
}
};
});
Put this in your .config or .run function.
First you'll need a service that you can inject into your controllers that has some idea of app authentication state. Persisting auth details with local storage is a decent way to approach it.
Next, you'll need to check the state of auth right before state changes. Since your app has some pages that need to be authenticated and others that don't, create a parent route that checks auth, and make all other pages that require the same be a child of that parent.
Finally, you'll need some way to tell if your currently logged in user can perform certain operations. This can be achieved by adding a 'can' function to your auth service. Can takes two parameters:
- action - required - (ie 'manage_dashboards' or 'create_new_dashboard')
- object - optional - object being operated on. For example, if you had a dashboard object, you may want to check to see if dashboard.ownerId === loggedInUser.id. (Of course, information passed from the client should never be trusted and you should always verify this on the server before writing it to your database).
angular.module('myApp', ['ngStorage']).config([
'$stateProvider',
function(
$stateProvider
) {
$stateProvider
.state('home', {...}) //not authed
.state('sign-up', {...}) //not authed
.state('login', {...}) //not authed
.state('authed', {...}) //authed, make all authed states children
.state('authed.dashboard', {...})
}])
.service('context', [
'$localStorage',
function(
$localStorage
) {
var _user = $localStorage.get('user');
return {
getUser: function() {
return _user;
},
authed: function() {
return (_user !== null);
},
// server should return some kind of token so the app
// can continue to load authenticated content without having to
// re-authenticate each time
login: function() {
return $http.post('/login.json').then(function(reply) {
if (reply.authenticated === true) {
$localStorage.set(_userKey, reply.user);
}
});
},
// this request should expire that token, rendering it useless
// for requests outside of this session
logout: function() {
return $http.post('logout.json').then(function(reply) {
if (reply.authenticated === true) {
$localStorage.set(_userKey, reply.user);
}
});
},
can: function(action, object) {
if (!this.authed()) {
return false;
}
var user = this.getUser();
if (user && user.type === 'admin') {
return true;
}
switch(action) {
case 'manage_dashboards':
return (user.type === 'manager');
}
return false;
}
}
}])
.controller('AuthCtrl', [
'context',
'$scope',
function(
context,
$scope
) {
$scope.$root.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
//only require auth if we're moving to another authed page
if (toState && toState.name.indexOf('authed') > -1) {
requireAuth();
}
});
function requireAuth() {
if (!context.authed()) {
$state.go('login');
}
}
}]
** DISCLAIMER: The above code is pseudo-code and comes with no guarantees **

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();
}]);

AngularJs - Create public pages and restric pages

I need to create some pages with restrictions and my code works fine but when I try to access pages that have an ID none works and automatically redirects me to the login. Can anyone tell me why?
function run($rootScope, $http, $location, $localStorage) {
// keep user logged in after page refresh
if ($localStorage.currentUser) {
$http.defaults.headers.common.Authorization = 'Bearer ' + $localStorage.currentUser;
}
// redirect to login page if not logged in and trying to access a restricted page
$rootScope.$on('$locationChangeStart', function (event, next, current, Auth) {
var publicPages = ['/login','/','/job','/job/:jobId'];
var restrictedPage = publicPages.indexOf($location.path()) === -1;
if (restrictedPage && !$localStorage.currentUser) {
$location.path('/login');
}
});
}
My config
.when("/job" , {
templateUrl: "app/components/job/views/job.html",
controller: "job"
})
.when("/job/:jobId" , {
templateUrl: "app/components/job/views/jobdetail.html",
controller: "job_detail"
})
I think your verification for public pages is wrong.
The path /job/:jobId is only a route.
Calling $location.path() returns the current path where the parameter is substituted and so returns /job/1 which is not in your publicPages array.
I would also recommend using ui router. It makes your life a lot easier and there are a lot tutorials how to use it (e.g. Handling basic route authorization in AngularJS)
If you are using ui.router then you can implement a scalable solution as follows. In this example any state start with app requires authentication. Login to the app using test#gmail.com as the email as password as 123. ui.router hooks does the trick!.
.run(($transitions, $state, $injector) => {
$transitions.onBefore({
to: 'app.**'
}, () => {
const $window = $injector.get('$window');
if (!$window.sessionStorage.getItem('user')) {
return $state.target('login', $state.transition.params());
}
return true
});
});
https://plnkr.co/edit/ZCN2hB34mMEmBJulyaAJ?p=preview

Restrict access to route with routeprovider unless variable as been set

I'm in the process of learning AngularJS, working on a more in-depth ToDo app. I'm having an issue with trying to limit access to a url or "route" using angular.
When you hit my dev url on my machine (todo.ang) it brings you to todo.ang/#/home, on this view you see the categories which have todos associated to each. EG (category = cat, cat has a todo of "feed", and "play"), when you click a category I'm calling the $scope.goToCategory function (seen in my JS fiddle) which sets a variable for my firebase ref then redirects you too /#/todo. This is working correctly.
My problem is, I don't want the user to be able to access /#/todo if the todoRef variable is still undefined. But it seems like even after $scope.goToCategory is called and todoRef is set to a firebase URL, the routerprovider never gets recalled to know that todoRef has been set to a different value so it always forces you back to /#/home.
code:
var todoRef = undefined;
if (todoRef !== undefined) {
$routeProvider.when('/todo', {
templateUrl: 'views/todo.html',
controller: 'TodoCtrl'
});
}
$scope.goToCategory = function(catId) {
test = catId;
todoRef = new Firebase("URL HERE");
$location.path('/todo');
}
I didn't include the entire file of code but if thats necessary, I can do that as well.
JSFiddle
All routes are only being set during the config phase.
what happens in your code is that 'todo' route is ignored during the initiation of ngRoute.
What you should do is to setup the route but have a resolve like so:
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/todo', {
templateUrl: 'views/todo.html',
controller: 'TodoCtrl',
resolve: {
todoRef: ['$q', function($q) {
return todoRef ? todoRef : $q.reject('no ref');
}]
}
});
}]);
If 'todoRef' is undefined the route is rejected.
Also you should consider moving 'todoRef' into a service and not on global scope.
You can also listen for route errors and for example redirect to home route:
app.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('$routeChangeError', function() {
$location.path('/home');
});
}]);

AngularJS: How to hide the template content until user is authenticated?

My app has 2 pages: main.html and login.html.
When not authenticated users go to /main they should be redirected to /login.
The problem is that main.html is rendered first, and after a second or so, when user authentication fails, login.html is rendered.
How could I prevent from main.html to be rendered until authentication succeeds?
Here is the relevant code (CoffeeScript):
angular.module('myApp', [...])
.config(['$routeProvider', ($routeProvider) ->
$routeProvider.when '/login',
templateUrl: 'html/login.html'
controller: LoginController
$routeProvider.otherwise
templateUrl: 'html/main.html'
controller: MainController
])
.run(['$rootScope', '$location', 'appService', ($rootScope, $location, app) ->
$rootScope.$on '$locationChangeStart', (event, newValue, oldValue) ->
return if newValue == '/login'
$.when(app.authenticate()).fail ->
$location.path '/login'
$rootScope.$apply()
])
angular.module('myApp.services').factory 'appService' , () ->
rootRef = new Firebase('https://myapp.firebaseio.com')
user: null
authenticate: ->
deferred = $.Deferred()
authClient = new FirebaseAuthClient rootRef, (error, user) =>
if error
# An error occurred while attempting login
#user = null
deferred.reject()
else if user
# User authenticated with Firebase
#user = user
deferred.resolve()
else
# User is logged out
#user = null
deferred.reject()
deferred.promise()
Well, I don't serve the template (in your case main.html) until the user is authenticated. I have a customized function on server for serving templates, which checks if the user is authenticated. If in the function I find out the user is not logged in, it returns response with 401 status code. In angular code I then hold the request until the authentication and then ask for the template again.
I was inspired to do this by this post: http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application
My solution to the same requirement was to define the following watch:
$rootScope.$watch(
function() {
return $location.path();
},
function(newValue, oldValue) {
if (newValue != '/login' && user is not logged in) {
$location.path('/login');
}
},
true);
in a controller associated with the body element of the index page (i. e. the page containing the ng-view directive).
One option is to hide the normal DOM and show an "Authenticating..." message, maybe with a spinner, to give the user some idea of why he/she is sitting there waiting for something to happen. In main.html, include something like:
<spinner ng-hide="appService.wrapper.user"></spinner>
<!-- everything else ng-show="appService.wrapper.user" -->
where <spinner></spinner> is an Angular directive that is replaced by your custom "Authenticating..." message, and user is a variable your appService makes available to MainController. Note that you may need to wrap user in an object within appService, like so:
.service('appService', function() {
var wrapper = {
user: null
};
function authenticate() {
// start the authentication and return the promise,
// but modify wrapper.user instead of user
}
return wrapper;
});
You'll also need to store either appService or appService.wrapper in the $scope variable of your MainController.

Resources