f6brkg
Last Updated: August 16, 2016
·
91.72K
· 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

Say Thanks
Respond

22 Responses
Add your response

7192
9e169c93f0e80af6ba9ff23ceb7ee7da

Was looking for this!

Thanks

over 1 year ago ·
7242

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 ·
7243

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 ·
7337
3f23a52c7bc87296e96b5a05547da0e0

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 ·
8387
99f531ec83a4be7bd4f3337fcdceb5bf

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 ·
9083

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

over 1 year ago ·
9524
154315bf6af2b8b9dc3501c639417806

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 ·
9526

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 ·
9834
D301430e5d97ec039880667f1386db04

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 ·
9835

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 ·
10037
6165aecb1a97c3ee6df433e38b49ca37

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 ·
10780
0 tk6jmqlmpb1xaqwf ckcmb1jy6kxjqufrb cmbig lk7gku7on9accbptmx8y1sippxinrlspyfr

Gracias Filipe, good guidelines to begin with ACLs

over 1 year ago ·
10953
B1fb3a82d68d8f147e8e30c389e91a76

Valeu, Filipe, ótima dica!

over 1 year ago ·
11509

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 ·
11872
Ad4d37ec2fae9e5197fa1df1441ee9b1

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 ·
11941
55063 3803523808619 773811908 o 1

@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 ·
14243

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

over 1 year ago ·
14684
189306 4332478785639 1790839652 n

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 ·
15238
F5dc910445775287403d09bf01e713cc

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 ·
16801
0 hokl9h8vxc4b3vi0exbg9wbv0xmwipe0oyka9wbxh3szpcgxk48gzi1oaeab8nw1djt7j2jkje1t

@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 ·
19978
0 jcf2q35hlfdry9orunhxq nfp80hy9xruv1pqcvju6irttekvachsg31gsx0jayp4ql rlixnfkf

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 ·
28068

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

over 1 year ago ·