I'm doing some onStateChange authorization like this:
angular
.module('app')
.run(['$rootScope', '$state', 'Auth', function ($rootScope, $state, Auth) {
$rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState, fromParams) {
console.log(toState);
if (!Auth.authorize(toState.data.allow)) {
console.log("Not authorized");
$state.go('app.auth.login', { redirect: toState.name });
}
console.log('Authorized');
});
}]);
Now, when I go to an unauthorized route, it acts as it should, up to a point. The authorization fails and it tries to redirect to app.auth.login. This is shown in the console.logs:
Object {url: "/", templateUrl: "modules/live/index.html", name: "app.live.index", data: Object}
app.routes.js:225 Not authorized
app.routes.js:222 Object {url: "/auth/login?redirect", templateUrl: "modules/auth/login.html", controller: "LoginController", name: "app.auth.login", data: Object}controller: "LoginController"data: Objectname: "app.auth.login"templateUrl: "modules/auth/login.html"url: "/auth/login?redirect"__proto__: Object
app.routes.js:229 Authorized
app.routes.js:229 Authorized
But the URL stays at the original state (the first attempted state), and I don't see the login screen. The LoginController never gets called (there's a console.log at the start that doesn't show.
If I visit /auth/login I see the login screen as intended.
Here's my app.routes.js:
/*****
* Auth
******/
.state('app.auth', {
abstract: true,
url: '',
template: '<div ui-view></div>',
data: {
allow: access.everyone
}
})
.state('app.auth.login', {
url: '/auth/login?redirect',
templateUrl: 'modules/auth/login.html',
controller: 'LoginController'
})
.state('app.auth.logout', {
url: '/auth/logout',
controller: 'LogoutController'
})
/*****
* Live
******/
.state('app.live', {
abstract: true,
url: '/live',
data: {
allow: access.live
}
})
.state('app.live.index', {
url: '/',
templateUrl: 'modules/live/index.html'
});
What can be causing this odd behaviour?
It happens in this cases, that we do forget to solve situation:
user is already (previously) redirected === user is going to $state.go('app.auth.login') ... no need to redirect
And we should not redirect in such case:
$rootScope.$on("$stateChangeStart",
function (event, toState, toParams, fromState, fromParams) {
if (toState.name === 'app.auth.login')
{
// get out of here, we are already redirected
return;
}
And once we do decide to redirect, we should stop current navigation with event.perventDefault();
if (!Auth.authorize(toState.data.allow)) {
// here
event.perventDefault();
$state.go('app.auth.login', { redirect: toState.name });
}
...
See more for example in this $stateChangeStart event continuing after state change or that Q & A with a working plunker
Related
The scenario is rather simple I have a local site that uses angular. I have my state provider set up us ui router lib.
If I type in http://localhost.com/#/lwejfpwef it will redirect me properly to my other wise clause to the state '/login'.
However I tried typing in http://localhost.com/lwjpoifwef it doesn't fire off any of the events that I have in my module config.
I have tried
$stateChangeSuccess
$stateChangeStart
$stateChangeError
$httpinterceptors
I'm not sure what I am doing wrong. It doesn't seem to hit any of the break points I put in there. Here is a snippet of what I have
angular.module('app').run(['$rootScope', '$state', 'service', function ($rootScope, $state, service) {
$rootScope.$on('$stateChangeSuccess', function (evt, to, params) {
alert('success');
});
$rootScope.$on('$stateChangeStart', function (event, to, params) {
alert('start');
});
$rootScope.$on('$stateChangeError', function (evt, toState, toParams, fromState, fromParams, error) {
$state.go('login');
});
}]);
angular.module('dealer-portal.core').config(CoreConfig);
function CoreConfig($stateProvider, $urlRouterProvider, $httpProvider) {
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'wwwroot/app/login/login.html',
data: {
displayName: 'Login'
}
})
.state('home', {
url: '/home',
templateUrl: 'wwwroot/app/login/login.html',
data: {
displayName: 'Home'
}
})
.state('error', {
url: '/login'
});
$urlRouterProvider.otherwise('/login');
}
$state.go should have the state name and not the url passed in
$state.go('login');
and NOT
$state.go('/login');
Also, you need to inject $stateParams in your run function
I am working on an IONIC application where I have checked if user is already logged in and if user is already logged then application should redirect on dashboard. This functionality is working well, but the application first showing login page for couple of seconds and then redirect to the dashboard.
app.js
$rootScope.$on("$locationChangeStart", function (event, next, current) {
var prefs = plugins.appPreferences;
prefs.fetch('iuserid').then(function (value) {
if (value != '') {
$state.go('app.dashboard');
}
});
.config(function ($stateProvider, $urlRouterProvider, $httpProvider) {
$stateProvider
.state('app', {
url: "/app",
abstract: true,
templateUrl: "templates/menu.html",
controller: 'AppCtrl'
})
.state('login', {
url: "/login",
templateUrl: "templates/login.html",
controller: 'LoginCtrl'
})
.state('app.dashboard', {
url: "/dashboard",
views: {
'menuContent': {
templateUrl: "templates/dashboard.html",
controller: 'DashboardCtrl'
}
}
})
;
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/login');
});
});
I don't know where I am making mistake.
Edit: I am able to authenticate and redirect to the dashboard but my problem is the login page displayed for few (up to 2) seconds and then redirect to the dashboard and I am working on IONIC application
Second Edit
I found the problem but don't know the solution. Preference work greatly in $ionicPlatform.ready but do not work in $locationChangeStart. And I need preference in $locationChangeStart because it runs before $ionicPlatformReady. I desperately need the solution.
I do the following:
in the app.js
.state('login', {
url: "/login",
templateUrl : "templates/session/login.html",
controller : 'SessionCtrl'
})
.state('register', {
url: "/register",
templateUrl : "templates/session/register.html",
controller : 'SessionCtrl'
})
.state('app', {
url: "/app",
abstract: true,
templateUrl : "templates/menu.html",
controller : 'AppCtrl',
onEnter: function($state, Auth){
if(!Auth.isLoggedIn()){
$state.go('login');
}
}
})
.state('app.main', {
url: "/main",
views: {
'menuContent': {
templateUrl : "templates/main_menu.html",
controller : "MainMenuCtrl"
}
}
})
$urlRouterProvider.otherwise('/app/main');
Auth is a factory, it stores the auth session in localstorage.
angular.module('auth.services', [])
.factory('Auth', function () {
if (window.localStorage['session']) {
var _user = JSON.parse(window.localStorage['session']);
}
var setUser = function (session) {
_user = session;
window.localStorage['session'] = JSON.stringify(_user);
}
return {
setUser: setUser,
isLoggedIn: function () {
return _user ? true : false;
},
getUser: function () {
return _user;
},
logout: function () {
window.localStorage.removeItem("session");
window.localStorage.removeItem("list_dependents");
_user = null;
}
}
});
I think you should listen to the event $stateChangeStart on the $rootScope, you can listen to this event during runtime, like this:
angular.module("myapp.permission").run($rootScope){
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
var loggedIn = true;
if(loggedIn) {
$state.go('app.dashboard');
}
}
}
Guys try this elegant solution:
if the user is not logged in and login is required it redirect to login.
if the user try to get to login and he already logged in, prevent him from doing it.
mark every path as "requireAuth" and it will work.
place your code in app.js
.run(function ($rootScope, $location, authService) {
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
if (to.requireAuth && !authService.isAuthed()) {
$location.path("/login");
}
else if (to.name == 'login' && authService.isAuthed()) {
ev.preventDefault();
$location.path("/dashboard");
}
});
})
Remove
$urlRouterProvider.otherwise('/loginpage')
at the bottom of routes.js file.
Inside your "run" block and outside of $ionicPlatform.ready you can use this:
//Check if user loggedin and redirect to login page if not
$rootScope.$on('$stateChangeStart', function (event, toState) {
//Check only if you are not in login page
if(toState.name.indexOf('app') !== -1 ) {
// If user is not logged, will redirect to login page, eg:
var prefs = plugins.appPreferences;
prefs.fetch('iuserid').then(function (value) {
if (value != '') {
$state.go('login');
}
});
}
});
After you must change your:
$urlRouterProvider.otherwise('/login');
for this:
$urlRouterProvider.otherwise('/dashboard');
The idea is directly route to dashboard and if is not logged in, will redirect to login page.
Using UI-router, when a user goes to a page that require's a login, after they login, I would like to redirect them back to the original url they came from.
So for example,
/account/profile --> LOGIN PAGE (/login) --> /account/profile
/someother/requiredLogin/path --> LOGIN PAGE (/login) --> /someother/requiredLogin/path
My routes:
$stateProvider
.state('accountProfile', {
url: '/account/profile',
data: {
requiresLogin: true
},
views: {
'': {
templateProvider: function($templateCache) {
return $templateCache.get('templates/profile.html');
}
}
},
})
.state('anotherPage', {
url: '/someother/path',
data: {
requiresLogin: true
},
views: {
'': {
templateProvider: function($templateCache) {
return $templateCache.get('templates/other.html');
}
}
},
})
Inside my application's run block I have:
.run(['$rootScope', '$state', 'LoginService',
function($rootScope, $state, LoginService) {
// Change title based on the `data` object in routes
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
var requiresLogin = toState.data.requiresLogin;
if (requiresLogin && !LoginService.check()) {
event.preventDefault();
$state.go('login');
}
});
}
])
As you can see, I do a basic job at adding a requiresLogin to each route, that just redirects always to login route. How can I keep track of the original URL and redirect them back to it AFTER they login?
Update your login state configuration to include default parameters for the name of the state to redirect to on login (toState) as well as any parameters passed with the original state change (toParams). If you don't define defaults, $state.params will always be empty when you check for them in your login controller - even if you provide values from your $stateChangeStart handler!
.state('login', {
data: {'requiresLogin': false},
params: {
'toState': 'profile', // default state to proceed to after login
'toParams': {}
},
url: 'login',
})
Modify your run block to capture and pass on the requested (unauthorized) state:
$state.go('login', {'toState': toState.name, 'toParams': toParams});
In your login controller success handler, the following will either send you to the state specified in $stateChangeStart or the default declared in your state configuration:
$state.go($state.params.toState, $state.params.toParams);
I would like to know if anyone has come up with a way to require state params when using ui router with angular.js and/or how to redirect if param is not specified. I would prefer not to have to put controller code in the stateProvider config, but am open to any suggestions.
I've come close with some things like:
if (typeof $stateParams.myId == 'undefined') {
...
}
But I cannot put that in a resolve, so I am not sure where or how to check that. I have an abstract state with children and the abstract state have the stateparam....
.state('mystate', {
abstract: true,
url: '/state/:permalink',
template: '<div ui-view></div>'
})
.state('mystate.config', {
url: '/config',
templateUrl: 'partials/conf/config.html'
})
TIA
OK...
Thanks to levi... I have this partially working:
.state('conference', {
abstract: true,
url: '/conference/:permalink',
data: {hasPerma: true},
module: 'conf',
template: '<div ui-view></div>',
})
.state('conference.config', {
url: '/config',
templateUrl: 'partials/conf/config.html'
})
... this is allowing me to check to toState data, but the fromState module (or data) do not show up for some reason:
.run(['$state', '$stateParams', '$rootScope', function($state, $stateParams, $rootScope) {
$rootScope.$on('$stateChangeStart', function(e, toState, toParams, fromState, fromParams) {
console.log(toState.data.hasPerma); //working
console.log('from: ' + fromState.module); //not showing up
})
}])
So if I got to the base url and /conference/hello/config I can see the to info, but not the from ;-(
I would like to have two home pages, the first would be for users who have not logged in and the second for users that are logged in.
This is my current set up:
.config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) {
$urlRouterProvider
.otherwise('/');
$locationProvider.html5Mode(true);
$httpProvider.interceptors.push('authInterceptor');
})
.factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) {
return {
// Add authorization token to headers
request: function (config) {
config.headers = config.headers || {};
if ($cookieStore.get('token')) {
config.headers.Authorization = 'Bearer ' + $cookieStore.get('token');
}
return config;
},
// Intercept 401s and redirect you to login
responseError: function(response) {
if(response.status === 401) {
$location.path('/login');
// remove any stale tokens
$cookieStore.remove('token');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
};
})
.run(function ($rootScope, $location, Auth) {
// Redirect to login if route requires auth and you're not logged in
$rootScope.$on('$stateChangeStart', function (event, next) {
if (next.authenticate && !Auth.isLoggedIn()) {
$location.path('/login');
}
});
});
.config(function ($stateProvider) {
$stateProvider
.state('main', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl',
title: 'Home',
mainClass: 'home',
headerSearch: true
});
});
How could I reconfigure this so I could do something like the following:
.config(function ($stateProvider) {
$stateProvider
.state('welcome', {
url: '/',
templateUrl: 'app/welcome/welcome.html',
controller: 'WelcomeCtrl',
title: 'Welcome',
mainClass: 'welcome',
isLoggedIn: false
});
$stateProvider
.state('main', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl',
title: 'Home',
mainClass: 'home',
isLoggedIn: true
});
});
Just wanted to show, how we can manage authentication driven access to states. Based on this answer and its plunker, we can enrich each state (which should be accessible only for authenticated users) with a data setting, explained here: Attach Custom Data to State Objects (cite:)
You can attach custom data to the state object (we recommend using a data property to avoid conflicts)...
So let's have some states with public access:
// SEE no explicit data defined
.state('public',{
url : '/public',
template : '<div>public</div>',
})
// the log-on screen
.state('login',{
url : '/login',
templateUrl : 'tpl.login.html',
controller : 'UserCtrl',
})
...
And some with private access:
// DATA is used - saying I need authentication
.state('some',{
url : '/some',
template : '<div>some</div>',
data : {requiresLogin : true }, // HERE
})
.state('other',{
url : '/other',
template : '<div>other</div>',
data : {requiresLogin : true }, // HERE
})
And this could be hooked on on the state change:
.run(['$rootScope', '$state', 'User', function($rootScope, $state, User)
{
$rootScope.$on('$stateChangeStart'
, function(event, toState, toParams, fromState, fromParams) {
var isAuthenticationRequired = toState.data
&& toState.data.requiresLogin
&& !User.isLoggedIn
;
if(isAuthenticationRequired)
{
event.preventDefault();
$state.go('login');
}
});
}])
See all that in action here
There is similar Q & A were I try to show the concept of redirecting Authenticated and Not Authenticated user:
Angular UI Router: nested states for home to differentiate logged in and logged out
maybe that could help to get some idea, how we can use ui-router, and its event '$stateChangeStart' to hook on our decision manager - and its forced redirecting...
the code should be something like this
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) { //, fromParams
console.log(toState.name + "," + fromState.name);
if(fromState.name === "") {
if (Auth.isLoggedIn()) {
$state.go('welcome');
event.preventDefault();
} else {
$state.go('home');
event.preventDefault();
} else {
if (toState.authenticate && !Auth.isLoggedIn()) {
$toState.go('login');
event.preventDefault();
}
}
}
so if user entering the application, then if he is logged in take him to welcome else take him to home.
once he is inside, then if he hits some route which needs auth.. then redirect him to login page..
sorry if i did not understood you requirement fully...
.config(function ($stateProvider,$rootScope) {
$stateProvider
.state('welcome', {
url: '/',
templateUrl: 'app/welcome/welcome.html',
controller: 'WelcomeCtrl',
onEnter: function() {
if (userIsLoggedIn()) {
$stateProvider.go('home');
}
});
});
I had problem like this and I solved it like this
.run(function ($rootScope, $location, AuthService) {
// start showing PRELOADER because we doing async call and we dont know when it will be resolved/rej
AuthService.checkLoginStatus().then(
(resp) => {
// fire logged in user event
$rootScope.$broadcast('userLogged',resp);
$location.path(YourHomePageUrl);
},
(err)=> {
// Check that the user is not authorized redirect to login page
$location.path(loginURL);
}
}).finally(
()=> {
// finnaly Set a watch on the $routeChangeStart
/// Hide PRELOADER
$rootScope.$on('$routeChangeStart',
function (evt, next, curr) {
if (!AuthService.isLoggedIn()) {
$location.path(art7.API.loginURL);
}
});
}
)
});
and im not using interceptor for handling 401 not authorized errors, thats my solution