2
votes

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;
    }]);
2
Could you show your attempt that is not working? Also, use localStorage, not $window.localStorage. Also, localStorage stores only strings, so you will want to use JSON.stringify() and JSON.parse() to store/retrieve your objects in localStorageholtc
@holtc Yes, give me a few minutes! Thank you!2b2a
@holtc It is up! Could you provide an explanation on why not to use $window.localStorage? My example uses it because that is what I used before.2b2a
I would side with @2b2a and not use $window.localStorage. I've never had good results with it, and in general wrapping something like window (which is a global variable) just to have it in Angular (which refers to that global variable) just seems like a waste.MBielski
yeah, make sure to JSON-ify the booleans too. And to be honest, you don't really need the booleans at all, just check for the presence of userData, and when you log out, make sure to remove userData from localStorageholtc

2 Answers

2
votes

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.

1
votes

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.