I'm tried to do user login authentication, I've made a service named AuthService
with a method isAuthenticated
in which I make a call to API to cross check the user is logged in or not (Also I'll user the user role in it later), and then I call this service in $rootScope.$on
but its not wait until my service get data from API, Here is my app.js
var app = angular.module('myApp', ['ngRoute', 'ui.router', 'ngAnimate', 'toaster', 'ui.bootstrap']);
app.config(['$routeProvider', '$locationProvider', '$stateProvider', '$urlRouterProvider',
function ($routeProvider, $locationProvider, $stateProvider, $urlRouterProvider) {
$stateProvider.
state('/', {
url: "/",
views: {
header: {
templateUrl: 'partials/common/header.html',
controller: 'authCtrl',
},
content: {
templateUrl: 'partials/dashboard.html',
controller: 'authCtrl',
}
},
title: 'Dashboard',
authenticate: true
})
.state('login', {
url: "/login",
views: {
content: {
templateUrl: 'partials/login.html',
controller: 'authCtrl',
}
},
title: 'Login',
authenticate: false
})
.state('dashboard', {
title: 'Dashboard',
url: "/dashboard",
views: {
header: {
templateUrl: 'partials/common/header.html',
controller: 'authCtrl',
},
content: {
templateUrl: 'partials/dashboard.html',
controller: 'authCtrl',
}
},
authenticate: true
});
$urlRouterProvider.otherwise("/");
//check browser support
if(window.history && window.history.pushState){
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
}
}])
.run(function ($rootScope, $location, $state, Data, AuthService) {
$rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState, fromParams) {
$rootScope.title = toState.title;
$rootScope.authenticated = false;
var userInfo = AuthService.isAuthenticated();
if (toState.authenticate && !AuthService.isAuthenticated()){
// User isn’t authenticated
$state.transitionTo("login");
event.preventDefault();
} else {
alert('here I am ='+$rootScope.authenticated);
}
});
});
Here is my service
app.factory('AuthService', function ($http, Data, $rootScope) {
var authService = {};
authService.isAuthenticated = function () {
Data.get('index.php/api/authentication/is_login').then(function (results) {
if (results.uid) {
$rootScope.authenticated = true;
$rootScope.uid = results.uid;
$rootScope.name = results.name;
$rootScope.email = results.email;
return results.uid;
} else {
return false;
}
});
}
return authService;
});
Tried lot of things but no luck so far, Please suggest a best way to do it, I've tried the resolve method in ui-routes but didn't work.
Thanks in advance.
The implementation that you currently have will not work because the Data.get()
function returns a Promise
, and is therefore asynchronous. Because of this, the route change will go ahead without waiting until your authentication service has returned any value.
To resolve your issue, you should handle this asynchronous logic in a particular way:
app.run(function ($rootScope, $location, $state, Data, AuthService) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
/**
* If the user is heading towards a state that requires
* authentication, assume that they are not authenticated,
* and stop them! Otherwise, let them through...
*/
if (toState.authenticate) {
event.preventDefault();
if (toState.name !== fromState.name) {
// Now check whether or not they are authenticated
AuthService.isAuthenticated().then(function () {
// Yes! They're authenticated, continue
$state.go(toState.name, toParams);
}, function () {
// Nope, this user cannot view this page
alert('You cannot view this page!');
});
}
} else {
return;
}
});
}
Edit: I have added a check statement
toState.name !== fromState.name
to break any redirect loops that may then cause endless calls to the authentication service. This was in response to a comment made by the original poster of the issue.
The trick is to prevent the route transition, by default. You are then able to perform as much asynchronous logic as you wish before the user is permitted to move any further on.
I would recommend adding a spinner, or a loading window, to alert the user to the fact that you are performing this authentication check. It may also be wise to prevent any further user actions on the page, but that's entirely up to you.
Note that the function AuthService.isAuthenticated()
now returns a Promise
, you will have to make a minor change to your authentication service to achieve this. Simply wrap your current logic like this:
app.factory('AuthService', function ($http, Data, $rootScope, $q) {
var authService = {};
authService.isAuthenticated = function () {
return $q(function (resolve, reject) {
Data.get('index.php/api/authentication/is_login').then(function (results) {
if (results.uid) {
$rootScope.authenticated = true;
$rootScope.uid = results.uid;
$rootScope.name = results.name;
$rootScope.email = results.email;
// Great, the user is authenticated, resolve the Promise
resolve(results.uid);
} else {
// The user does not appear to be authenticated, reject the Promise
reject();
}
});
});
};
return authService;
});
I hope that this resolves your issue, Promises can be a pain, but they can also be a pretty powerful tool. There was a good thread on GitHub discussing a number of potential solutions around this issue that can be found here: https://github.com/angular-ui/ui-router/issues/1399.