Angular: Dealing with reload page and User Authentication - javascript

I've been working in a application that log a user. Once that the user is logged, the info of the user is stored in a service and a cookie is stored with the auth token.
Like this:
angular
.module('MyModule')
.service('AuthService', service);
service.$inject = ['$cookieStore', '$resource'];
function service($cookieStore, $resource){
var self = this,
user = null;
self.loginResource = $resource('my_path_to_login_resource');
self.login = login;
self.getUser = getUser;
self.reloadUser = reloadUser;
function login(_userCredentials){
var userResource = new self.loginResource(_userCredentials);
return userResource.$save()
.then(setUser);
}
function setUser(_userResponse){
user = _userResponse.toJSON();
}
function getUser(){
return user;
}
function reloadUser(_token){
return self.loginResource()
.get(_token)
.then(setUser);
}
}
Using ui-router when I need deal with the routes of the app I do this:
angular
.module('MyModule')
.run(runFn);
runFn.$inject = ['$state', '$rootScope', 'AuthService', '$cookieStore'];
function runFn($state, $rootScope, AuthService, $cookieStore) {
$rootScope.$on('$stateChangeSuccess', stateTransitioned);
function stateTransitioned(e, toState, toParams, fromState, fromParams) {
var pageRealoaded = fromState.name? false : true;
if(AuthService.getUser()){
//DEALING WITH STATES TRANSITIONS
}
else{
if($cookieStore.get('auth_token') && pageRealoaded){
//ENSURE THAT THE USER IS LOGGED WHEN THE STATE IS CHANGED
AuthService.reloadUser.then(function(){
$state.go(toState.name);
})
.catch($state.go.bind(null,'login'));
//I DON'T KNOW HOW AVOID THAT THE STATE IS LOADED UNTIL THE USER
//HAVE BEEN LOGGED
}
else{
$state.go('login');
}
}
}
}
When the page is reloaded, using the stored token, I try to waiting that user have been login, and then, if it's success, redirect to state toState.name, and if error, redirects to login.
My questions:
1. How to avoid that the state is loaded until the user have been login?
2. My architecture for dealing for this case are correct? Suggestions for better sctructure?

I would recommend you use the resolve functionallity of ui-router to to ensure that user is ALWAYS authenticated before entering a state.
See the ui-router wiki here:
https://github.com/angular-ui/ui-router/wiki#resolve
Something like this should prevent state entry before authentication.
.state('secure', {
resolve: {
user: function(AuthService, $state) {
// State will only be entered if this resolves.
return AuthService.resolveUser()
.catch(function(error) {
$state.go('login');
})
}
},
template: '<div>Secret Page</div>
})
I also threw together this pen with a working example:
http://codepen.io/marcus-r/pen/eZKbYV?editors=1010

Finally, for my first question, I create the following example that shows how I prevent that the state change until the user is logged successfully.
http://codepen.io/gpincheiraa/pen/zqLYZz
In this example, when i go to the "state3", i wait until the user is login, and then redirects to the state
angular
.module('exampleApp', ['ui.router']);
angular
.module('exampleApp')
.config(configFn);
configFn.$inject = ['$stateProvider', '$urlRouterProvider'];
function configFn($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise('state1');
$stateProvider
.state('state1', {
url: '/state1',
controller: ['$state', function($state){
var vm = this;
vm.current = $state.current.name;
vm.goTwo = function(){$state.go('state2');};
}] ,
controllerAs: 'one',
template: '<h1>{{one.current}}</h1><button ng-click="one.goTwo()">Go to state 2</button>'
})
.state('state2', {
url: '/state2',
controller: ['$state', function($state){
var vm = this;
vm.current = $state.current.name;
vm.goThree = function(){$state.go('state3');};
}] ,
controllerAs: 'two',
template: '<h1>{{two.current}}</h1><button ng-click="two.goThree()">Go to state 3</button>'
})
.state('state3', {
url: '/state3',
controller: ['$state', function($state){
var vm = this;
vm.current = $state.current.name;
}] ,
controllerAs: 'three',
template: '<h1>{{three.current}}</h1>'
})
}
angular
.module('exampleApp')
.run(runFn);
runFn.$inject = ['$rootScope','$timeout','$state'];
function runFn($rootScope, $timeout, $state){
var logged = false;
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
console.log(toState.name);
if(toState.name === 'state3' && !logged){
//Fake like a user take a 1 second to login in server
$timeout(function(){
logged = true;
console.log('login ok');
$state.go(toState.name);
},1000)
console.log('while the user not loggin, prevent the state change');
event.preventDefault();
}
})
}

Related

Use local storage to store AngularJS data

I am currently using $rootScope to store user information and whether or not the user is logged in. I have tried using $window.localStorage, but with no success. My goal is to have items in my navbar appear through an ng-show once a user is logged on, have their username appear in the navbar, individual user profile view, all users view, etc. I need a persistent login. I have the navbar working with $rootscope, but whenever I try and transition over to $window.localStorage, it fails. Here is the code using $rootScope:
mainModule
angular.module('mainModule', [
'ui.router',
...
])
.config(configFunction)
.run(['$rootScope', '$state', 'Auth', function($rootScope, $state, Auth) {
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.requireAuth && !Auth.getAuthStatus()) {
console.log('DENY');
event.preventDefault();
$state.go('login');
} else if (Auth.getAuthStatus() || !Auth.getAuthStatus()) {
console.log('ALLOW');
}
});
}]);
Auth Factory
angular.module('authModule').factory('Auth', ['$http', '$state', function authFactory($http, $state) {
var factory = {};
var loggedIn = false;
var userData = {};
factory.getAuthStatus = function() {
$http.get('/api/v1/auth')
.success(function(data) {
if (data.status == true) {
loggedIn = true;
} else {
loggedIn = false;
}
})
.error(function(error) {
console.log(error);
loggedIn = false;
});
return loggedIn;
}
return factory;
}]);
Login Controller
function SigninController($scope, $rootScope, $http, $state) {
$scope.userData = {};
$scope.loginUser = function() {
$http.post('api/v1/login', $scope.userData)
.success((data) => {
$scope.userData = data.data;
$rootScope.loggedIn = true;
$rootScope.userData = data;
$state.go('home');
})
.error((error) => {
console.log('Error: ' + error);
});
};
}
Nav Controller
function NavbarController($scope, Auth) {
$scope.loggedIn = Auth.getAuthStatus();
}
EDIT EDIT EDIT
Here is how I am using local storage. These are the only things that changed.
Login Controller
function SigninController($scope, $window, $http, $state) {
$scope.userData = {};
$scope.loginUser = function() {
$http.post('api/v1/login', $scope.userData)
.success((data) => {
$scope.userData = data.data;
$window.localStorage.setItem('userData', angular.toJson(data));
$window.localStorage.setItem('loggedIn', true);
$state.go('home');
})
.error((error) => {
console.log('Error: ' + error);
});
};
}
Auth Factory
angular
.module('authModule')
.factory('Auth', ['$http', '$window', '$state', function authFactory($http, $window, $state) {
var factory = {};
factory.getAuthStatus = function() {
$http.get('/api/v1/auth')
.success(function(data) {
if (data.status == true) {
$window.localStorage.setItem('loggedIn', true);
} else {
$window.localStorage.setItem('loggedIn', false);
}
})
.error(function(error) {
console.log(error);
$window.localStorage.setItem('loggedIn', false);
});
return $window.localStorage.getItem('loggedIn');
}
return factory;
}]);
I see a potential problem with your use of localStorage.getItem('loggedIn').
Because localStorage only stores strings, what you get back is actually a stringified version of the boolean that you put in. If the string 'false' gets returned, your check of !Auth.getAuthStatus() in main module for example will always evaluate to boolean false because any non-empty string in JavaScript is "truthy".
i.e. !'false' === false (the same as !true === false)
You can get over this by using JSON.parse on the value in localStorage. e.g. JSON.parse(localStorage.getItem('loggedIn')) would parse the string 'false' to the Boolean false.
Simply replace $window.localStorage with window.localStorage and you should be fine.
For example:
function SigninController($scope, $window, $http, $state) {
$scope.userData = {};
$scope.loginUser = function() {
$http.post('api/v1/login', $scope.userData)
.success((data) => {
$scope.userData = data.data;
window.localStorage.setItem('userData', angular.toJson(data));
window.localStorage.setItem('loggedIn', true);
$state.go('home');
})
.error((error) => {
console.log('Error: ' + error);
});
};
}
This being said, storing authenticated status in localStorage (or sessionStorage) is not a good path to go down. Both key/value pairs can be read in the developer pane and then altered (aka spoofed) via the console. A better solution is to return a unique value (GUID) after a successful login and store it in a cookie (set to expire in a short amount of time, like 20 minutes) that can be read on the server and verified there. You can and should use $cookie for this. Your user login state should be controlled server-side, never client-side. The client should always have to prove that it is authenticated.
To persist login, create a service that handles your visitor and let that service handle the login/logout and provide the proof of being logged in. That proof of being logged in should always be a private value that is held internally by the service and not accessible outside of it.
(function () {
'use strict';
var visitorModelService = ['$http', function ($http) {
var loggedIn = false,
visitorModel = {
login:function(){
//do login stuff with $http here
//set loggedIn to true upon success
},
loggedIn:function(){
return loggedIn;
},
logout:function(){
//do logout stuff with $http here
//no matter what, set loggedIn to false
}
};
return visitorModel;
}];
var module = angular.module('models.VisitorModel', []);
module.factory('VisitorModel', visitorModelService);
}());
Doing this, you can simply check for visitor.loggedIn in your ng-show and have everything centralized. Such as:
<a ng-click='visitor.logout' ng-show='visitor.loggedIn'>Log Out</a>
Better yet, put the elements that are only visible to authenticated users in a div tag and hide/show them en-mass.

AngularJS: Promise in app.run() stuck in loop forever

I'm using app.run() in my AngularJS app to check whether a user is logged in before displaying the site to block access to various sites for non-registered users. I tried doing it with a promise because before, whenever I reloaded the page the isLoggedIn function would return false the getStatus hasn't returned the answer from the server yet.
Now using the promise, the site just calls itself in a loop forever, I guess because the process just repeats itself when the promise is resolved. Where am I going wrong and how could I fix this? Thanks in advance, help is much appreciated!
This is my code in app.js:
app.run(function($rootScope, $state, authService){
$rootScope.$on('$stateChangeStart', function(event, next, nextParams, from, fromParams){
event.preventDefault();
authService.getUserStatus().then(function(){
console.log(authService.isLoggedIn());
if(next.access.restricted && !authService.isLoggedIn()){
$state.go('index', {}, { reload: true });
} else {
$state.go(next, {}, { reload: true });
}
});
});
});
Here's the service authService.js:
(function(){
var app = angular.module('labelcms');
app.factory('authService', ['$q', '$timeout', '$http', function($q, $timeout, $http){
var user = null;
var isLoggedIn = function(){
if(user){
return true;
} else {
return false;
}
};
var getUserStatus = function(){
var deferred = $q.defer();
$http.get('/api/user/status')
.success(function(data){
if(data.status){
user = data.status;
deferred.resolve();
} else {
user = false;
deferred.resolve();
}
})
.error(function(data){
console.log('Error: ' + data);
user = false;
deferred.resolve();
});
return deferred.promise;
};
return ({
isLoggedIn: isLoggedIn,
getUserStatus: getUserStatus,
login: login,
logout: logout,
signup: signup
});
}]);
})();
It loops because every time you execute $state.go(next, {}, { reload: true }); it will hit your $rootScope.$on again.
I would check if we actually are on restricted route before you go into your security service.
app.run(function($rootScope, $state, authService){
$rootScope.$on('$stateChangeStart', function(event, next, nextParams, from, fromParams){
if(!next.access.restricted) return;
authService.getUserStatus().then(function(){
console.log(authService.isLoggedIn());
if(!authService.isLoggedIn()){
$state.go('index', {}, { reload: true });
});
});
});

10 digest() iteration reached error in login procedure

This is my code to implement in my application a login procedure. The application has to verify the cookie set by the server and continue with the login procedure by redirect the user to canvas state. My issue is that I get the above mentioned error. Actually it works that is the login is made successfully but I would like to get rid of this error. I guess that the error should be in the $stateChangeStart but I don't know how to fix it. any idea?
(function() {
'use strict';
angular
.module('app', [
'ui.router',
'ngResource',
'ngCookies',
'app.login'
])
.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise('/login');
})
.run(function($rootScope, AuthService, RedirectService) {
$rootScope.$on('$stateChangeStart', function(event, toState) {
if (!AuthService.isAuthenticated()) {
// the user isn't authenticated
event.preventDefault();
// redirect to the server side
RedirectService.redirectToAuth();
}
});
});
})();
(function() {
'use strict';
angular
.module('app.login')
.factory('AuthService', Auth);
Auth.$inject = ['Cookie', 'Session'];
function Auth(Cookie, Session) {
return {
login: function(params) {
// here set the session with params passed by the server
Session.create(params.id, params.data.id, params.data.make, params.data.name);
},
isAuthenticated: function() {
// check cookie here set in the server side
return Cookie.exist();
}
};
}
})();
(function() {
'use strict';
angular
.module('app.login')
.service('Cookie', Cookie);
Cookie.$inject = ['$cookies'];
function Cookie($cookies) {
this.authCookie = $cookies.__cookie;
this.exist = function() {
return (this.authCookie ? true : false);
};
}
})();
(function() {
'use strict';
angular
.module('app.login')
.factory('RedirectService', Redirect);
Redirect.$inject = ['$window'];
function Redirect($window) {
return {
redirectToAuth: function() {
// redirect the user to the server for auth
$window.location.href = "http://" + $window.location.host + "/auth/facebook";
}
};
}
})();
(function() {
'use strict';
angular
.module('app.login')
.controller('LoginController', Login);
// here inject what function Login needs
Login.$inject = ['$rootScope', '$scope', '$state', '$stateParams', 'AuthService'];
function Login($rootScope, $scope, $state, $stateParams, AuthService) {
var params = {
id: $stateParams.userid,
data: {
id: $stateParams.modelid,
make: $stateParams.modelmake,
name: $stateParams.modelname
}
};
$scope.login = function(params) {
AuthService.login(params);
// activate the canvas state
$state.go('canvas');
};
// run the login function to set the Session user with data passed by the server
$scope.login(params);
}
})();
Maybe this can help a little more:
/*
We are using the below urlRouterProvider.otherwise() because of:
https://github.com/angular-ui/ui-router/issues/600
*/
$urlRouterProvider.otherwise(function($injector, $location) {
var $state = $injector.get('$state');
$state.go('login');
});
With this code you can still use the otherwise(), the disadvantage of using when() is that other unknown routes will not match. Above code solved all of our infinite loops.
Fixed the issue
$urlRouterProvider.otherwise('/login');
caused all this issue.
removed it and replaced with when() solved the issue

Change state of angular app from resolve method of state provider

I'm using ui-router in my angular application. Currently I've two routes /signin & /user.
Initially it shows /signin when the user clicks on the login button, I'm sending a ajax request and getting the user id. I'm storing the user id in localstorage and changing the state to /user.
Now, what I want, if a user is not loggedin, and user changes the addressbar to /user, it'll not change the view, instead it'll change the addressbar url to /signin again.
I'm try to use resolve, but it's not working. My code is:-
module.exports = function($stateProvider, $injector) {
$stateProvider
.state('signin', {
url: '/signin',
template: require('../templates/signin.html'),
controller: 'LoginController'
})
.state('user', {
url: '/user/:id',
template: require('../templates/user.html'),
resolve:{
checkLogin: function(){
var $state = $injector.get('$state');
console.log("in resolve");
if (! window.localStorage.getItem('user-id')) {
console.log("in if")
$state.go('signin');
}
}
},
controller: 'UserController'
})
}
Please help me to solve this problem.
I don't think it's allowed to change states in the middle of a state transition.
So, the way to address it is to have the checkLogin resolve parameter (I changed it below to userId) to be a function that either returns a value or a promise (in this case, a rejected promise, if you can't get the user-id).
You'd then need to handle this in $rootScope.$on('$stateChangeError') and check the error code.
resolve: {
userId: function ($q, $window) {
var userId = $window.localStorage.getItem('user-id');
if (!userId) {
return $q.reject("signin")
}
return userId;
}
}
And redirect in the $stateChangeError handler:
$rootScope.$on('$stateChangeError', function (event, toState, toParams, fromState, fromParams, error) {
if (error === "signin") {
$state.go("signin");
}
});
If someone has this problem, you can solve it, using timeout service. It will put state switching call at the end of queue.
Also, you should use promises. Rejecting it will prevent initialization of that state:
resolve:{
checkLogin: function(){
var deferred = $q.defer();
var $state = $injector.get('$state');
if (!window.localStorage.getItem('user-id')) {
$timeout(function(){$state.go('signin');});
deferred.reject();
} else {
deferred.resolve();
}
return deferred.promise;
}
},

Pass $scope object to route, keep if refresh

Lets say i list all users in a list, when i click a user i want to route to a new view and get the data for the selected person.
What is the preferred way? Should i move the data i already got when i listed the users or should i create a new server call?
My first thought is to pass the data, but the problem with this is that the data the gets lost if the user refreshes the page.
What is the best practice to solve this?
Small example:
(function() {
var app = angular.module('app');
var controllerId = 'app.controllers.views.userList';
app.controller(controllerId, [
'$scope', 'UserService',function ($scope, userService) {
var vm = this;
vm.users = [];
userService.getAllUsers().success(function (data) {
vm.users= data.users;
});
var gotoUser = function(user) {
// Pass the user to UserDetail view.
}
}
]);
})();
<div data-ng-repeat="user in vm.users" ng-click="vm.gotoUser(user)">
<span>{{customer.firstname}} {{customer.lastname}}</span>
</div>
i now list the user details in UserDetail view, this view is now vulnerable against a browser refresh.
Typically most people just create a new server call, but I'll assume you're worried about performance. In this case you could create a service that provides the data and caches it in local storage.
On controller load, the controller can fetch the data from the service given the route params and then load the content. This will achieve both the effect of working on page refresh, and not needing an extra network request
Here's a simple example from one of my apps, error handling left out for simplicity, so use with caution
angular.
module('alienstreamApp')
.service('api', ['$http', '$q','$window', function($http, $q, $window) {
//meta data request functions
this.trending = function() {
}
this.request = function(url,params) {
var differed = $q.defer();
var storage = $window.localStorage;
var value = JSON.parse(storage.getItem(url+params))
if(value) {
differed.resolve(value);
} else {
$http.get("http://api.alienstream.com/"+url+"/?"+params)
.success(function(result){
differed.resolve(result);
storage.setItem(url+params,JSON.stringify(result))
})
}
return differed.promise;
}
}]);
I would say that you should start off simple and do a new server call when you hit the new route. My experience is that this simplifies development and you can put your effort on optimizing performance (or user experience...) where you will need it the most.
Something like this:
angular.module('app', ['ngRoute', 'ngResource'])
.factory('Users', function ($resource) {
return $resource('/api/Users/:userid', { userid: '#id' }, {
query: { method: 'GET', params: { userid: '' }, isArray: true }
});
});
.controller("UsersController",
['$scope', 'Users',
function ($scope, Users) {
$scope.loading = true;
$scope.users = Users.query(function () {
$scope.loading = false;
});
}]);
.controller("UserController",
['$scope', '$routeParams', 'Users',
function ($scope, $routeParams, Users) {
$scope.loading = true;
$scope.user = Users.get({ userid: $routeParams.userid }, function () {
$scope.loading = false;
});
$scope.submit = function () {
$scope.user.$update(function () {
alert("Saved ok!");
});
}
}]);
.config(
['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider
.when('/users', {
templateUrl: '/users.html',
controller: 'UsersController'
})
.when('/users/:userid', {
templateUrl: '/user.html',
controller: 'UserController'
})
.otherwise({ redirectTo: '/users' });
}
]
);

Categories

Resources