tpie
tpie

Reputation: 6221

JWT Authentication in Angular - Multiple authentication levels

I am building an angular app with a sails.js backend. I've incorporated this JWT authentication into the app.
https://github.com/Foxandxss/sails-angular-jwt-example

I am still exploring exactly how the authentication works and I am currently trying to figure out how to add multiple authorization levels. It comes pre-built with 0 and 1, and I want to add an admin level above that.

It handles the existing authorization levels as follows: Each route is assigned an authorization level, via a constant. So all user routes get this access property assigned at the data property in the state:

    $stateProvider
          .state('user', {
            abstract: true,
            template: '<ui-view/>',
            data: {
              access: AccessLevels.user
            }
          })

The AccessLevels.user is pulled from a constant key-value pair:

angular.module('app')
  .constant('AccessLevels', {
      anon: 0,
      user: 1
  });

Whenever a route is navigated to, it checks the data.access property to see what the access level is for that particular route. If it comes back as a route requiring authentication, it checks localStorage for a token. If there is a token, the route proceeds, otherwise it boots you.

Here is the function that is called on stateChangeStart:

authorize: function(access) { //<- 'access' will be whatever is in the data property for that state
        if (access === AccessLevels.user) {
          return this.isAuthenticated(); // <- this just grabs the token
        } else {
          return true;
        }
      }

So what is the simplest way to add an additional layer?
I obviously need to put auth_level value into the user model. But then what? What would be the best way to add this functionality and maintain the integrity of the existing authentication (I am concerned about security)?

Upvotes: 6

Views: 1896

Answers (2)

tpie
tpie

Reputation: 6221

Firstly, Erik Gillespie got me headed in the right direction. Thanks!

Here are some things I had to do to add an additional level of authentication to my angular/sails app:

  • I had to add a 'level' field to my user model. The authentication module I am using already had some variables set for anonymous users (0) and regular users (1). So now my user model stores whether you are level 1 or level 2.
  • I had to update the correlating constant in the angular app that get's referenced when doing some authentication stuff. I just added admin: 2 to the existing list of user types.
  • I had to use a private claim in my JWT to store the users level in their session token. This is necessary to confirm that the user actually has the authority they say they have whenever they try to do something (usually CRUD). Their authority level is checked against the authority level stored in their token. Which leads to the next thing I had to do...
  • I had to create two policies. Sails has a neat config structure where in your API you can create middleware (it calls them policies). You just fill the policies folder with whatever middleware you might possibly need. Then in the sails config folder, there is a corresponding policies.js file where you can assign middleware to each specific function of each controller. Very cool. So I created a middleware that checks to make sure a user is the authority level they say they are, and another one to make sure they are admin (if they are trying to do admin stuff). Then I just added the appropriate middleware to whatever functions I wanted to restrict.
  • If a person is logged in, I have two separate dashboards as well as some pages that regular users can't go to. So angular needs to check if you are admin or just a user and direct you to the appropriate dashboard, for example. Rather than mess with my navigation, I created a function like this in app.run:
        $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
              if (toState.url == '/dashboard') {
                var dash = Auth.level(); // <- this is a service I made that checks the users level and returns the appropriate state to go to for that user
                event.preventDefault();
                $state.go(dash);
              }
    I added whatever routes were appropriate for each user type, and then when they hit that url it pushes them to the right one. My routes also all have a data object attached to them that specifies the level necessary to access. So it checks the users level against that to make sure they can even go to that part of the app or not. That get's handled with a similar stateChangeStart function.

Upvotes: 1

Erik Gillespie
Erik Gillespie

Reputation: 3959

You can use private claims to hold authorizations granted by the server. Private claims let you put whatever other sorts of data in JWT that you want.

I've never used node-jsonwebtoken (which appears to be what sails.js uses for JWTs), but it looks like you can just work directly with the JWT payload to access values that you would expect to be present, so you could do something like this:

// Assign authorization on server after successful authentication of an admin
token.auth = AccessLevels.admin;

// Read authorization on client
authorize: function(access) {
    if (access === AccessLevels.anon) {
        return true;
    }
    // Assumes the JWT token is returned by isAuthenticated()
    // (Sorry, not familiar with Sails.js or the JWT example)
    return this.isAuthenticated().auth === access;
}

After authentication, any subsequent request should verify the user's authorization as well, probably by adding an addition policy similar to "tokenAuth" in the example you provided, but that verifies the auth claim on the supplied JWT matches the authorization required to invoke whatever function is about to be called.

Upvotes: 2

Related Questions