Last Updated: September 27, 2021
·
106K
· filipefmelo

AngularJS Access Control and Authentication

Hi guys, as you probably ran into some kind of problem regarding AngularJS authentication, like: "How can I tell if the user is authenticated?" or "Can I make a page not open if the user does not have access to it?", well I've ran into this a thought it might be nice to share my finding with you.

In order to make a user be remembered by our system, we used a service we called SessionService and it looks something like this:

Session Service

angular.module('MyApp.Services').service('SessionService', function(){
    var userIsAuthenticated = false;

    this.setUserAuthenticated = function(value){
        userIsAuthenticated = value;
    };

    this.getUserAuthenticated = function(){
        return userIsAuthenticated;
    });
});

This will allow us to check if the current user has already logged in or not (keep in mind that one will still need to store these values on a cookie or something similar to prevent page reload from resetting the values).

The next requirement is an object where we can define what are the routes and which ones are public and which ones are not:

Routes Object

window.routes =
{
    "/welcome": {
        templateUrl: 'partials/welcome.html', 
        controller: 'WelcomeCtrl', 
        requireLogin: false
},
    "/user-details": {
        templateUrl: 'partials/userDetails.html', 
        controller: 'UserDetailsCtrl', 
        requireLogin: true
    }
};

Notice that this object is very similar to the routes object we usually use in the "config" of our application but this one has "requireLogin" (and may have a lot more, use your imagination!).

Now, the other thing we need is an App, in wich to use the previous service and routes object, something like this:

My App

angular.module('MyApp', [
    'MyApp.Services'
]);

MyApp.config(['$routeProvider', function($routeProvider){

    //this loads up our routes dynamically from the previous object 
    for(var path in window.routes) {
        $routeProvider.when(path, window.routes[path]);
    }
    $routeProvider.otherwise({redirectTo: '/welcome'});

}]).run(function(){

    $rootScope.$on("$locationChangeStart", function(event, next, current) {
        for(var i in window.routes) {
            if(next.indexOf(i) != -1) {
                if(window.routes[i].requireLogin && !SessionService.getUserAuthenticated()) {
                    alert("You need to be authenticated to see this page!");
                    event.preventDefault();
                }
            }
        }
    });

});

The $locationChangeStart, unlike, $routeChangeStart, allows you to cancel navigation before it happens using event.preventDefault();

Summary (TLDR):
With only three steps we were able to create authenticated routes on our system, check the code above :P

23 Responses
Add your response

Was looking for this!

Thanks

over 1 year ago ·

hi there, scuse the perhaps dumb question (I'm an angular n00b); but would it not possible to then as a client using the console; simply to set the this.setUserAuthenticated = true? and thus get around the security?

over 1 year ago ·

Hi rosscdh, yes, you are right. However this is just a basic skeleton for an application. It should be connected to an API and all (and I mean ALL) of the verifications for authorizations should be done there. Javascript is just not safe... client side has it's perks but also it's flaws. Thank you for your comment, it should be useful to others as well.

over 1 year ago ·

Why would you attach routes to the window?

Presumably they dont change during the life-cycle of the session? Angular can be a little touchy about globals but its generally not a great idea to be attaching things to the window when it isnt necessary anyway.

In your example you could attach your routes straight to MyApp or add it as part of a config file and then attach it in the config

over 1 year ago ·

Great basic auth system, but I agree with mattstyles. Attaching routes to the window when you can just attach them inside the $routeProvider doesn't make a lot of sense to me.

over 1 year ago ·

Hi Filipi, (td bem?)
What AngularJS version of this tutorial?
Thanx

over 1 year ago ·

Even simple copy paste of the code is not working for me.. not sure why. Like the $rootScope is not defined anywhere. Even the Session service has a syntax error I think.

over 1 year ago ·

Hi there,

These are just guidelines and cannot be used as copy/paste. You have to write your own code. This works in all AngularJS versions.

over 1 year ago ·

Hello filipefmelo ,

Can't user change the angular javascript "requireLogin: true" to "requireLogin: false" and access the page. I am really confused if it is possible or not.
Please suggest.
Thanks

over 1 year ago ·

Hi,

Yes, of course that is possible. But this is only a behavioural flag. The real security relies in the communication with the API.

over 1 year ago ·

Thank you for the helpful ideas!

This quote I like:
"These are just guidelines and cannot be used as copy/paste. You have to write your own code. This works in all AngularJS versions."

because it seems self-contradictory

I tried to use this pattern but spotted a little shortcoming to this approach, which is that the condition

next.indexOf(i) != -1

Seems to assume that the path is going to be a substring of the 'next' location, which will fail on a parameterized route, e.g. "/users/:name"

(unless I am missing something)

over 1 year ago ·

Gracias Filipe, good guidelines to begin with ACLs

over 1 year ago ·

Valeu, Filipe, ótima dica!

over 1 year ago ·

What @blak3mill3r said.

Instead of creating a hook on $locationChangeStart, create a hook on $routeChangeStart, which is passed the next and current route.

This way you can simply access the requireLogin property, like this:

$rootScope.$on("$routeChangeStart", function(event, next, current) {
    if(next.requireLogin) {
        // Auth/session check here
        event.preventDefault();
    }
});

Hope this helps.

over 1 year ago ·

As said in the post, you can't do "event.preventDefault()" inside the routeChangeStart handler. But the code isn't flawless. As some other people suggested, it's not that clean to have a global variable with your routes. What we did was define a constant in Angular ( myApp.constant('routes', {}) )

Another problem with the code described here is when I have a route with parameters.
route = "/myPage/:id"
next = "/myPage/4"
indexOf will return -1 and it'll fail

/* Edit */
You can't get the constants when in a run block so that would not work in this situation.

over 1 year ago ·

@elwinarents Thanks for that! I'm going to rewrite this and make a tutorial of it this one has a lot of errors in it and I don't think he's managing it.

over 1 year ago ·

After reading this post, I have gotten idea about how to set and check authentication per routes. Thank you.

over 1 year ago ·

Im Angular newbie, but i have some questions...

This code appear work fine if the client is working without a backend, but the backend is who say if we have permission or not... This system is like a cached permission. and the question is...

Not a good practice do a login get the token of the server, keep it in the client and use it on each request? if the server return forbidden, redirect to login.

over 1 year ago ·

To combine The origional author's work, @elwinarens work, and the idea that a parameterized path really messes with things, try this bit of code...

// ...
run(function ($rootScope, $route, SessionService) {
    var rts = $route.routes;
    $rootScope.$on('$locationChangeStart', function (event, next) {
        next = /* TODO: cleanup next */;
        var my_access = SessionService.getUserAuthenticated();
        for (var k in rts) {
            if (
                rts[k].hasOwnProperty('regexp') && 
                rts[k].regexp.test(next) &&
                /* TODO: check your access here */
            ) {
                alert('you need authentication to view this page');
                event.preventDefault();
                break;
            }
        }
    });
}).
// ...

I left the access portion checking as a comment, as I have a slightly more complicated scheme than authenticated or not-authenticated.

This allows you to get rid of the global routes object, as it uses the internal routes, and the parameterized routes are matched via the internal regexp that angular creates for us.

Still needs some work, but hopefully it will get people thinking...

over 1 year ago ·

@clouddueling did you ever rewrite up a tutorial? Was curious if you had an example available. I'm just starting out with AngularJS and am looking for a few different examples of login/authentication.

over 1 year ago ·

Great article,
But i don't know what i have this error :
" TypeError: undefined is not a function "
regarding to this line :
" if(next.indexOf(i) != -1) "

Any help :)

over 1 year ago ·

Thanks for your example. it was helpful for me to think the right way about self coding custom autentication service

over 1 year ago ·

Best guide brother.

over 1 year ago ·