3

Before I go to far I am hoping someone can tell me if I am handling this situation correctly or if there is a far easier way I am just missing. We have a web page made using AngularJS and Web API. We are now dealing with authentication of users when they try to go to certain pages. We have a module called permissions we use for this currently shown below.

angular.module('permissions', []).factory('permissions', function (LoginApi, $q) {
//this is the variable which will hold the user, fetch it from the server - will return null if not authenticated
var storedUser;

function currentUser() {
    var deferred = $q.defer();

    if (!storedUser) {
        storedUser = LoginApi.get(function () {
            deferred.resolve(storedUser);
        })
    }
    else {
        deferred.resolve(storedUser);
    }

    return deferred.promise;
}

//define the permissions object that's exposed publicly
return {

    setUser: function (user) {
        storedUser = user;
    },
    isInRole: function (roleName) {
        var promise = currentUser();
        promise.then(function() {
            if (!storedUser || !storedUser.roles) {
                return false;
            }

            //if roleName is in currentUserRoles, then return true
            if (storedUser.roles.indexOf(roleName) > -1) {
                return true;
            } else {
                return false;
            }
        }, function(reason) {
            return false;
        });
    },
    isLoggedIn: function () {
        var promise = currentUser();
        promise.then(function() {
            if (!storedUser) {
                return false;
            } else {
                return true;
            }
        }, function(reason) {
            return false;
        });
    },
    firstName: function () {
        var promise = currentUser();
        promise.then(function () {
            if (!storedUser) {
                return '';
            }

            return storedUser.firstName;
        }, function (reason) {
            return '';
        });
    },
    lastName: function () {
        var promise = currentUser();
        promise.then(function () {
            if (!storedUser) {
                return '';
            }

            return storedUser.lastName;
        }, function (reason) {
            return '';
        });
    },
    email: function () {
        var promise = currentUser();
        promise.then(function () {
            if (!storedUser) {
                return '';
            }

            return storedUser.email;
        }, function (reason) {
            return '';
        });
    }
};
});

As you can see I am using a promise to make my calls wait for me to get the user if there is one that is logged in. I have this working but now my issue is that in any function on my web site that deals with permissions it looks like I will need to add promises and deferrals too because currently the calls are not waiting for the permissions functions to run. An example of a place this is being used is in my routeChange event on the rootScope shown below

$rootScope.$on("$routeChangeStart", function (event, next, current) {

    if (next.access == "All")
    {
        //do nothing
    }
    else if (next.access == "User") {
        if ($rootScope.permissions.isLoggedIn()) {
            //do nothign they have permissions required
        } else {
            // not going to #login, we should redirect now
            $location.path("/login");
        }
    }
    else if (next.access == "Admin") {
        if ($rootScope.permissions.isInRole('Admin_Base')) {
            //do nothign they have valid permissions
        } else {
            // not going to #login, we should redirect now
            $location.path("/403");
        }
    }
});

So basically even though inside of permissions my functions defer til the user gets returned. The functions that call these functions do not defer so it basically gets a false returned every time when checking permissions. I think I can get around this by adding promises to the calls to permissions but I am wondering if there is an easier way to handle this that I am missing.

4 Answers 4

1

Try broadcasting an event in the login callback.

$rootScope.$broadcast('event:login-confirmed');

Then just use that event for your "on" watch.

$rootScope.$on("event:login-confirmed", function (event, next, current) { ...
Sign up to request clarification or add additional context in comments.

1 Comment

That would work if I wanted this to fire 1 time but I am looking for something to check roles each time you go to a different route. This normally is not an issue because once you are logged in we have your information but if you hit F5 on a page we lose the login info we have stored on the front end and have to retrieve it again which is why I am having to deal with promises.
1

(Disclosure: I'm one of the developers of UserApp)

One way to solve it is to use the third-party service UserApp, together with the AngularJS module.

Check out the getting started guide, or take the course on Codecademy. Here's some examples of how it works:

  • Login form with error handling:

    <form ua-login ua-error="error-msg">
        <input name="login" placeholder="Username"><br>
        <input name="password" placeholder="Password" type="password"><br>
        <button type="submit">Log in</button>
        <p id="error-msg"></p>
    </form>
    
  • Signup form with error handling:

    <form ua-signup ua-error="error-msg">
        <input name="first_name" placeholder="Your name"><br>
        <input name="login" ua-is-email placeholder="Email"><br>
        <input name="password" placeholder="Password" type="password"><br>
        <button type="submit">Create account</button>
        <p id="error-msg"></p>
    </form>
    

    ua-is-email means that the username is the same as the email.

  • How to specify which routes that should be public, and which route that is the login form:

    $routeProvider.when('/login', {templateUrl: 'partials/login.html', public: true, login: true});
    $routeProvider.when('/signup', {templateUrl: 'partials/signup.html', public: true});
    

    The .otherwise() route should be set to where you want your users to be redirected after login. Example:

    $routeProvider.otherwise({redirectTo: '/home'});

  • Log out link:

    <a href="#" ua-logout>Log Out</a>

  • Access user properties:

    User info is accessed using the user service, e.g: user.current.email

    Or in the template: <span>{{ user.email }}</span>

  • Hide elements that should only be visible when logged in:

    <div ng-show="user.authorized">Welcome {{ user.first_name }}!</div>

  • Show an element based on permissions:

    <div ua-has-permission="admin">You are an admin</div>

And to authenticate to your back-end services, just use user.token() to get the session token and send it with the AJAX request. At the back-end, use the UserApp API to check if the token is valid or not.

If you need any help, just let me know :)

Comments

0

This is a reply to your updated question that doesn't involve promises.

What about using angular cookies to cache the user? Just be sure to clear the cookie on logout (and any other event that is clearing the server's cookie).

You'll need to include the ngCookies module.

See: http://docs.angularjs.org/api/ngCookies.$cookies for more details.

I couldn't comment directly on the updated question since I don't have enough reputation.

1 Comment

I ended up using cookies to make this work. the only thing I don't like about it is that it opens up the possibility for someone to be able to tamper with the cookies since they would then be on the users computer but what I did was use the cookies until the response came back and then the response sets the variables with the info from the server
0

You're definitely on point with using promises and the $routeChangeStart event, but you're still missing one or two crucial pieces. The first of which is the proper pattern for getting the promises to issue, return, resolve, and call back to the right places, in the right order. The basic pattern goes like this:

// 1.) Issue the promise
var deferred = $q.defer();
// 2.) Initiate the asynch process
LoginAPI.get(function(result) {
  // Resolve the promise with the result passed into the callback
  deferred.resolve(result);
}
// 3.) Return promise
return deferred.promise;

I usually use the ui-router and states rather than vanilla AngularJS routes, so I'm just going to sub in pseudocode here until I get around to looking up the actual syntax. The point is, you want to interrupt your route changes and wait for the promise to resolve, then restart them.

// Initialize a flag within the closure so we don't get stuck in a loop
var pending = false
// Catch the $routeChangeStart event
$rootScope.$on('$routeChangeStart',function(event,next,current) {
  // Check and flip the flag so we don't do this again when we restart
  if (pending = !pending) {
    // Interrupt the route change and prevent it from proceeding.
    event.preventDefault();
    // Initiate auth check with callback
    getUser().then(function(result) {
      // if authorized
      if (result.authd) {
        // restart the original route change
        // followInitialRouteTo(next);
      } else {  // if not authorized
        // redirect to login route
        // goTo(loginRoute);
      }
    }
  } else {  // second pass, pending was true
    // do nothing and let the event be handled by angular
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.