UI-Router Resolve In Infinite Loop On Promise Failure - javascript

I'm trying to redirect users based on authentication so within the resolve of each route state I've placed my service for checking for authentication so in this case login and dashboard.
.state('login', {
url: "/login",
abstract: true,
templateUrl: '/app/login/login.html'
})
// Default login state
.state('login.index', {
url: "",
templateUrl: "/app/login/views/login.html",
controller: 'LoginController',
controllerAs: 'loginCtrl',
resolve: {
UserAuth: session
}
})
.state('dashboard', {
url: "/dashboard",
abstract: true,
templateUrl: '/app/dashboard/dashboard.html',
resolve: {
UserAuth: session
}
})
// Default dashboard state
.state('dashboard.index', {
url: "",
templateUrl: '/app/dashboard/views/page1.html',
controller: 'Page1Controller',
controllerAs: 'page1Ctrl',
resolve: {
UserAuth: session
}
})
// with multiple states like the child route above...
Within my .config for routing I also set up session for use above, which on promise success or existence of a session $state.go to dashboard, else on promise failure $state.go to login.
.config(function($stateProvider, $urlRouterProvider) {
var session = ['$q', '$location', '$state', 'SessionService',
function( $q, $location, $state, SessionService ) {
return SessionService.session()
.then(function( session ) {
// Check location path as $state is not resolved yet
var location = $location.path();
/**
* Check user authentication to dashboard and handle
* state change if session already exists
*/
if( location.indexOf('login') === 1 ) {
// Redirect to default dashboard view
$state.go('dashboard.index');
}
}, function( error ) {
// Check location path as $state is not resolved yet
var location = $location.path();
/**
* Check for unauthorized access to dashboard and handle
* state change if not located on login view
*/
if( location.indexOf( 'dashboard' ) === 1 ) {
// Redirect to default login view
$state.go('login.index');
}
// Don't propagate error since it has been handled and it prevents page
// return $q.reject( error );
});
}];
But, it's running through an infinite loop of 403 errors when I let the session time out, which is the correct server response for promise failure. It enters the if statement, and runs $state.go('login.index'), but keeps looping and never replaces the state with login. Can anyone see what I've done wrong? Works in all cases except session time out, then doesn't redirect.

I dropped together some code that I used in a past project that displays how I setup Authentication and interception of 401/403 on Angular side of things.
https://gist.github.com/jfornoff/4637069c398dc2f5b881
Note that especially the authorization of users can not be reliably done in the frontend and always have your backend authenticate and not give out data blindly to some end-point that your frontend should be using ;-) But I guess that's pretty self-explanatory.
Hope I could help you out!
Bye,
Jan

Related

how to check firebase auth before routeProvider send to page

I want a route to check and see if a Firebase user is signed in before it redirects someone to the /map.html page.
.when('/map', {
templateUrl: 'views/map.html',
controller: 'MapCtrl',
resolve: {
loggedIn: onlyLoggedIn()
}
});
I have tried using resolve (see code above) but I only get errors from the onlyLoggedIn() function that say "user is not defined". User is defined and if I try to pass in user to the function I get an error "Unknown provider: user"
var onlyLoggedIn = function ($location,$q,user) {
var deferred = $q.defer();
console.log("well: "+user);
if (user) {
console.log("user is signed in ");
deferred.resolve();
} else {
deferred.reject();
$location.url('/signup');
}
return deferred.promise;
};
How should I go about checking if a user is signed in with firebase before routing them to the /map page?
Firebase gives me the user variable and in 'other' places in my app it works, so the problem isn't user not being defined, but my onlyLoggedIn function not "getting" it(?)
Security rules in Firebase can prevent the page from data from being viewed without permission. You need a client-side solution to redirect the page. You do in routes.
This is complete code for the Angular Firebase
This is simple code demonstration. I hope that will work for you.
angular.module('myApp.routes', ['ngRoute', 'simpleLogin'])
.constant('ROUTES', {
'/home': {
templateUrl: 'partials/home.html',
controller: 'HomeCtrl',
resolve: {
// forces the page to wait for this promise to resolve before controller is loaded
// the controller can then inject `user` as a dependency. This could also be done
// in the controller, but this makes things cleaner (controller doesn't need to worry
// about auth status or timing of displaying its UI components)
user: ['simpleLogin', function(simpleLogin) {
return simpleLogin.getUser();
}]
}
},
'/chat': {
templateUrl: 'partials/chat.html',
controller: 'ChatCtrl'
},
'/login': {
templateUrl: 'partials/login.html',
controller: 'LoginCtrl'
},
'/account': {
templateUrl: 'partials/account.html',
controller: 'AccountCtrl',
// require user to be logged in to view this route
// the whenAuthenticated method below will resolve the current user
// before this controller loads and redirect if necessary
authRequired: true
}
})
/**
* Adds a special `whenAuthenticated` method onto $routeProvider. This special method,
* when called, invokes the requireUser() service (see simpleLogin.js).
*
* The promise either resolves to the authenticated user object and makes it available to
* dependency injection (see AuthCtrl), or rejects the promise if user is not logged in,
* forcing a redirect to the /login page
*/
.config(['$routeProvider', function($routeProvider) {
// credits for this idea: https://groups.google.com/forum/#!msg/angular/dPr9BpIZID0/MgWVluo_Tg8J
// unfortunately, a decorator cannot be use here because they are not applied until after
// the .config calls resolve, so they can't be used during route configuration, so we have
// to hack it directly onto the $routeProvider object
$routeProvider.whenAuthenticated = function(path, route) {
route.resolve = route.resolve || {};
route.resolve.user = ['requireUser', function(requireUser) {
return requireUser();
}];
$routeProvider.when(path, route);
}
}])
// configure views; the authRequired parameter is used for specifying pages
// which should only be available while logged in
.config(['$routeProvider', 'ROUTES', function($routeProvider, ROUTES) {
angular.forEach(ROUTES, function(route, path) {
if( route.authRequired ) {
// adds a {resolve: user: {...}} promise which is rejected if
// the user is not authenticated or fulfills with the user object
// on success (the user object is then available to dependency injection)
$routeProvider.whenAuthenticated(path, route);
}
else {
// all other routes are added normally
$routeProvider.when(path, route);
}
});
// routes which are not in our map are redirected to /home
$routeProvider.otherwise({redirectTo: '/home'});
}])
/**
* Apply some route security. Any route's resolve method can reject the promise with
* { authRequired: true } to force a redirect. This method enforces that and also watches
* for changes in auth status which might require us to navigate away from a path
* that we can no longer view.
*/
.run(['$rootScope', '$location', 'simpleLogin', 'ROUTES', 'loginRedirectPath',
function($rootScope, $location, simpleLogin, ROUTES, loginRedirectPath) {
// watch for login status changes and redirect if appropriate
simpleLogin.watch(check, $rootScope);
// some of our routes may reject resolve promises with the special {authRequired: true} error
// this redirects to the login page whenever that is encountered
$rootScope.$on("$routeChangeError", function(e, next, prev, err) {
if( angular.isObject(err) && err.authRequired ) {
$location.path(loginRedirectPath);
}
});
function check(user) {
if( !user && authRequired($location.path()) ) {
$location.path(loginRedirectPath);
}
}
function authRequired(path) {
return ROUTES.hasOwnProperty(path) && ROUTES[path].authRequired;
}
}
]);

Pass Json data to another route template in angular js

After login I want to pass the user details to dashboard?How it possible in angular js?
Login.js
mySchoolApp.controller('loginController', ['$scope', '$http', function($scope, $http) {
this.loginForm = function() {
let encodedString = 'uname=' +this.username +'&pwrd=' +this.password;
sessionStorage.user = encodedString;
console.log(sessionStorage.user)
window.location.href = 'dashboard.html';
}
}]);
In console I'm getting the value.
How to get the user details in dashboard.html page?
You should use ng-route to achieve this.Angular isn't designed to work like this
Here is sample
$stateProvider
.state('app', {
abstract: true,
url: "",
template: '<ui-view/>'
})
.state('app.home', {
url: "/",
templateUrl: "partials/main_page.html",
resolve: {
skipIfLoggedIn: skipIfLoggedIn
}
}).state('app.dashboard', {
url: "/dashboard",
templateUrl: "partials/dashboard.html",
controller: 'DashboardCtrl',
activePage:'dashboard',
resolve: {
loginRequired: loginRequired
}
You can store it in a localstorage.So you can use angular-local-storage Angular module for that.
How to set :
myApp.controller('MainCtrl', function($scope, localStorageService) {
//...
function submit(key, val) {
return localStorageService.set(key, val);
}
//...
});
How to Get :
myApp.controller('MainCtrl', function($scope, localStorageService) {
//...
function getItem(key) {
return localStorageService.get(key);
}
//...
});
You should use router module ui-router or ng-router in order to use angualrjs logic in that sense but then your pages are going to be loaded via ajax and regular session http authentication can not be applied.
If that's the case then use angular service provider and let me know to edit my answer.
If you'd like to keep data across pages and not using database or server.
Then what is left as options are: sessionStorage and localStorage.
The localStorage keeps data permanently until browser cache deletes it while the other one obviously for the session.
sessionStorage.setItem('myCat', 'Tom');
If you want to keep js collection like object or array first stringify it:
var user = {pass:'moo', name: 'boo'};
sessionStorage.setItem('userDetais', JSON.stringify(user));

AngularJS: Preventing user from accessing a certain state

I'm building an app using sails.js backend and angular in frontend. I'm trying to prevent the user from accessing the admin control page if he's not authorized. I've run into couple of answers already, but none of them seem to fully work.
At the moment in my app.js, I have
$stateProvider
.state('home', {
url: "/home",
templateUrl: "home/homeTemplate.html",
controller: 'homeController'
})
.state('adminPage', {
url: "/adminPage",
templateUrl: "adminPage/adminTemplate.html",
controller: 'adminPageController',
resolve: {
validate: function($q, $sails, $location) {
var defer = $q.defer();
$sails.get("/user/getCurrentUser")
.success(function(response) {
if (response.user.accessAdminPage) {
defer.resolve();
}
else {
defer.reject("Access blocked");
$location.path('/');
}
return defer.promise;
})
}
}
})
The current code is partially working; The problem at the moment is, that when the unauthorized user first logs in and lands on the home page, and then accesses localhost:1337/#/adminPage, he actually reaches the page. The url in the address bar changes to localhost:1337/#/home but the user isn't redirected. Now the weird part is, when accessing the home page afterwards through the navbar and trying to access the admin page again, the user IS redirected to the home page as intended (although there's an annoying 'flash' while the page is reloaded).
For other people asking, this kind of handling has worked, and I'm wondering what I may have missed and generally any reasons for why my current solution isn't working.
You are returning promise from success function, this will never work.
You should return defered.promise (promise object) from outside success function.
CODE
$stateProvider
.state('home', {
url: "/home",
templateUrl: "home/homeTemplate.html",
controller: 'homeController'
})
.state('adminPage', {
url: "/adminPage",
templateUrl: "adminPage/adminTemplate.html",
controller: 'adminPageController',
resolve: {
validate: function($q, $sails, $location) {
var defer = $q.defer();
$sails.get("/user/getCurrentUser")
.success(function(response) {
if (response.user.accessAdminPage) {
defer.resolve();
} else {
defer.reject("Access blocked");
$location.path('/');
}
});
return defer.promise;
}
}
});
Hopefully this could help you, Thanks.
With the solution given by pankajparkar, the issue is that you will have to reply the logic in each state declaration. I recommend you to check the user's authorization in the onStateChangeStart event
angular.module('myApp', ['ui.router'])
.run(function($rootScope, AuthService){
$rootScope.$on('$stateChangeStart',
function(event, next, nextParams, prev, prevParams) {
AuthService.isNotAutorized()
.then(function() {
event.preventDefault();
$state.go('defaultState');
});
});
});

Can $routeProvider be replaced by $stateProvider or vice versa and how?

I have found a perfectly working and advanced Angularfire-Seed (that encompasses Simple Login). This framework uses ngRoute and $routeProvider to route the appropriate view and to make sure that certain pages are only shown when the user has logged in.
Now I want to integrate this with the Ionic Framework, such that I have an Ionic App that uses this Authentication system. However, Ionic uses $stateProvider.
My question then becomes: how do I combine these two? I guess one of the two has to go and be replaced by the other. But how do you do this?
Below are the two projects that I want to combine:
1. Angular-Fire Seed using $routeProvider (routes.js)
"use strict";
angular.module('myApp.routes', ['ngRoute', 'simpleLogin'])
.constant('ROUTES', {
'/home': {
templateUrl: 'partials/home.html',
controller: 'HomeCtrl',
resolve: {
// forces the page to wait for this promise to resolve before controller is loaded
// the controller can then inject `user` as a dependency. This could also be done
// in the controller, but this makes things cleaner (controller doesn't need to worry
// about auth status or timing of displaying its UI components)
user: ['simpleLogin', function(simpleLogin) {
return simpleLogin.getUser();
}]
}
},
'/chat': {
templateUrl: 'partials/chat.html',
controller: 'ChatCtrl'
},
'/login': {
templateUrl: 'partials/login.html',
controller: 'LoginCtrl'
},
'/account': {
templateUrl: 'partials/account.html',
controller: 'AccountCtrl',
// require user to be logged in to view this route
// the whenAuthenticated method below will resolve the current user
// before this controller loads and redirect if necessary
authRequired: true
}
})
/**
* Adds a special `whenAuthenticated` method onto $routeProvider. This special method,
* when called, invokes the requireUser() service (see simpleLogin.js).
*
* The promise either resolves to the authenticated user object and makes it available to
* dependency injection (see AuthCtrl), or rejects the promise if user is not logged in,
* forcing a redirect to the /login page
*/
.config(['$routeProvider', function($routeProvider) {
// credits for this idea: https://groups.google.com/forum/#!msg/angular/dPr9BpIZID0/MgWVluo_Tg8J
// unfortunately, a decorator cannot be use here because they are not applied until after
// the .config calls resolve, so they can't be used during route configuration, so we have
// to hack it directly onto the $routeProvider object
$routeProvider.whenAuthenticated = function(path, route) {
route.resolve = route.resolve || {};
route.resolve.user = ['requireUser', function(requireUser) {
return requireUser();
}];
$routeProvider.when(path, route);
}
}])
// configure views; the authRequired parameter is used for specifying pages
// which should only be available while logged in
.config(['$routeProvider', 'ROUTES', function($routeProvider, ROUTES) {
angular.forEach(ROUTES, function(route, path) {
if( route.authRequired ) {
// adds a {resolve: user: {...}} promise which is rejected if
// the user is not authenticated or fulfills with the user object
// on success (the user object is then available to dependency injection)
$routeProvider.whenAuthenticated(path, route);
}
else {
// all other routes are added normally
$routeProvider.when(path, route);
}
});
// routes which are not in our map are redirected to /home
$routeProvider.otherwise({redirectTo: '/home'});
}])
/**
* Apply some route security. Any route's resolve method can reject the promise with
* { authRequired: true } to force a redirect. This method enforces that and also watches
* for changes in auth status which might require us to navigate away from a path
* that we can no longer view.
*/
.run(['$rootScope', '$location', 'simpleLogin', 'ROUTES', 'loginRedirectPath',
function($rootScope, $location, simpleLogin, ROUTES, loginRedirectPath) {
// watch for login status changes and redirect if appropriate
simpleLogin.watch(check, $rootScope);
// some of our routes may reject resolve promises with the special {authRequired: true} error
// this redirects to the login page whenever that is encountered
$rootScope.$on("$routeChangeError", function(e, next, prev, err) {
if( angular.isObject(err) && err.authRequired ) {
$location.path(loginRedirectPath);
}
});
function check(user) {
// used by the changeEmail functionality so the user
// isn't redirected to the login screen while we switch
// out the accounts (see changeEmail.js)
if( $rootScope.authChangeInProgress ) { return; }
if( !user && authRequired($location.path()) ) {
$location.path(loginRedirectPath);
}
}
function authRequired(path) {
return ROUTES.hasOwnProperty(path) && ROUTES[path].authRequired;
}
}
]);
2. Ionic Framework (app.js)
// Ionic Starter App
// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngCordova', 'firebase'])
.run(function($ionicPlatform) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if (window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if (window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleDefault();
}
});
})
.config(function($stateProvider, $urlRouterProvider) {
// Ionic uses AngularUI Router which uses the concept of states
// Learn more here: https://github.com/angular-ui/ui-router
// Set up the various states which the app can be in.
// Each state's controller can be found in controllers.js
$stateProvider
// setup an abstract state for the tabs directive
.state('tab', {
url: "/tab",
abstract: true,
templateUrl: "templates/tabs.html"
})
// Each tab has its own nav history stack:
.state('tab.chats', {
url: '/chats',
views: {
'tab-chats': {
templateUrl: 'templates/tab-chats.html',
controller: 'ChatsCtrl'
}
}
})
.state('tab.account', {
url: '/account',
views: {
'tab-account': {
templateUrl: 'templates/tab-account.html',
controller: 'AccountCtrl'
}
}
})
.state('tab.login', {
url: '/login',
views: {
'tab-login': {
templateUrl: 'templates/tab-login.html',
controller: 'LoginCtrl'
}
}
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/tab/dash');
})
Update (16/01/2015, 10.46)
I have tried the following (replacing routeProvider by urlRouterProvider), it didnt work (nothing is showing up)
index.html, included:
<ion-nav-view></ion-nav-view>
routes.js
"use strict";
angular.module('myApp.routes', ['ngRoute', 'simpleLogin'])
.constant('ROUTES', {
'/home': {
templateUrl: 'templates/home.html',
controller: 'HomeCtrl',
resolve: {
// forces the page to wait for this promise to resolve before controller is loaded
// the controller can then inject `user` as a dependency. This could also be done
// in the controller, but this makes things cleaner (controller doesn't need to worry
// about auth status or timing of displaying its UI components)
user: ['simpleLogin', function(simpleLogin) {
return simpleLogin.getUser();
}]
},
authRequired: true
},
'/chat': {
templateUrl: 'templates/chat.html',
controller: 'ChatCtrl',
authRequired: true
},
'/camera': {
templateUrl: 'templates/camera.html',
controller: 'CameraCtrl',
authRequired: true
},
'/login': {
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
},
'/account': {
templateUrl: 'templates/account.html',
controller: 'AccountCtrl',
// require user to be logged in to view this route
// the whenAuthenticated method below will resolve the current user
// before this controller loads and redirect if necessary
authRequired: true
}
})
/**
* Adds a special `whenAuthenticated` method onto $routeProvider. This special method,
* when called, invokes the requireUser() service (see simpleLogin.js).
*
* The promise either resolves to the authenticated user object and makes it available to
* dependency injection (see AuthCtrl), or rejects the promise if user is not logged in,
* forcing a redirect to the /login page
*/
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
// credits for this idea: https://groups.google.com/forum/#!msg/angular/dPr9BpIZID0/MgWVluo_Tg8J
// unfortunately, a decorator cannot be use here because they are not applied until after
// the .config calls resolve, so they can't be used during route configuration, so we have
// to hack it directly onto the $routeProvider object
$urlRouterProvider.whenAuthenticated = function(path, route) {
route.resolve = route.resolve || {};
route.resolve.user = ['requireUser', function(requireUser) {
return requireUser();
}];
$urlRouterProvider.when(path, route);
}
}])
// configure views; the authRequired parameter is used for specifying pages
// which should only be available while logged in
.config(['$stateProvider', '$urlRouterProvider', 'ROUTES', function($stateProvider, $urlRouterProvider, ROUTES) {
angular.forEach(ROUTES, function(route, path) {
if( route.authRequired ) {
// adds a {resolve: user: {...}} promise which is rejected if
// the user is not authenticated or fulfills with the user object
// on success (the user object is then available to dependency injection)
$urlRouterProvider.whenAuthenticated(path, route);
}
else {
// all other routes are added normally
$urlRouterProvider.when(path, route);
}
});
// routes which are not in our map are redirected to /home
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/tab/dash');
}])
/**
* Apply some route security. Any route's resolve method can reject the promise with
* { authRequired: true } to force a redirect. This method enforces that and also watches
* for changes in auth status which might require us to navigate away from a path
* that we can no longer view.
*/
.run(['$rootScope', '$location', 'simpleLogin', 'ROUTES', 'loginRedirectPath',
function($rootScope, $location, simpleLogin, ROUTES, loginRedirectPath) {
// watch for login status changes and redirect if appropriate
simpleLogin.watch(check, $rootScope);
// some of our routes may reject resolve promises with the special {authRequired: true} error
// this redirects to the login page whenever that is encountered
$rootScope.$on("$stateChangeError", function(e, next, prev, err) {
if( angular.isObject(err) && err.authRequired ) {
$location.path(loginRedirectPath);
}
});
function check(user) {
// used by the changeEmail functionality so the user
// isn't redirected to the login screen while we switch
// out the accounts (see changeEmail.js)
if( $rootScope.authChangeInProgress ) { return; }
if( !user && authRequired($location.path()) ) {
$location.path(loginRedirectPath);
}
}
function authRequired(path) {
return ROUTES.hasOwnProperty(path) && ROUTES[path].authRequired;
}
}
]);

Basic resolve for Angular routing

I'm creating a very basic login system with Angular JS. Basically, after a login form is submitted, credentials are checked against a database using PHP and Mysqli. If all is ok, it returns successful and I set a variable like $rootScope.isLoggedIn = true and user details in other similar variables.
Now I'm trying to work out the best way to use resolve with my routes so that only logged in users or users with correct permissions can access a page (including on page reload!)
I just can't get my head around how these resolves work. My routing looks like this so far (the dashboard page needs user to be logged in):
app.config(function($routeProvider) {
$routeProvider
//login
.when('/', {
templateUrl : 'framework/views/login.html',
controller : 'LoginCtrl',
title: 'Admin Login'
})
//dashboard
.when('/dashboard', {
templateUrl : 'framework/views/dashboard.html',
controller : 'DashboardCtrl',
title: 'Dashboard',
resolve: { ? }
})
//catch all redirect
.otherwise({ redirectTo: '/' });
});
Resolve will open your route with a resolved variable (that can be injected into controller) as soon as it gets value (instantly or when returned promise will be resolved). See:
resolve: {
myVar: function(service, $rootScope, etc){
return service.obj; // or primitive, or promise or something else
}
}
.controller('DashboardCtrl', function(myVar){
console.log(myVar); // that service.obj goes here
}
That can't prevent route from loading (as I understand, please correct me if I'm wrong).
For your case it's better to use something like this:
.when('/', {
templateUrl : 'framework/views/login.html',
controller : 'LoginCtrl',
title: 'Admin Login',
authenticate: false
})
.when('/dashboard', {
templateUrl : 'framework/views/dashboard.html',
controller : 'DashboardCtrl',
title: 'Dashboard',
authenticate: true
})
$rootScope.$on("$routeChangeStart", function(event, toState){
if (toState.authenticate && !$rootScope.isLoggedIn){
$location.path('/');
}
});

Categories