Skip to content

Usage in ui router states

Aditya Agarwal edited this page Sep 20, 2017 · 10 revisions

Before start

Make sure you are familiar with:

Overview

  1. Introduction
  2. Property only and except
  3. Single permission/role
  4. Multiple permissions/roles
  5. Dynamic access
  6. Property redirectTo
  7. Single rule redirection
  8. Multiple rule redirection
  9. Dynamic redirection rules
  10. State permission inheritance
  11. State redirection inheritance

Introduction

Now you are ready to start working with controlling access to the states of your application. In order to restrict any state angular-permission rely on ui-router's data property, reserving key permissions allowing to define authorization configuration.

Permissions object accepts following properties:

Property Accepted value
only [String|Array|Function]
except [String|Array|Function]
redirectTo [String|Function|Object]

Property only and except

Property only:

  • is used to explicitly define permission or role that are allowed to access the state
  • when used as String contains single permission or role
  • when used as Array contains set of permissions and/or roles
  • when used as Function or Promise returns single/set of permissions and/or roles

Property except:

  • is used to explicitly define permission or role that are denied to access the state
  • when used as String contains single permission or role
  • when used as Array contains set of permissions and/or roles
  • when used as Function or Promise returns single or set of permissions and/or roles

🔥 Important
If you combine both only and except properties you have to make sure they are not excluding each other, because denied roles/permissions would not allow access the state for users even if allowed ones would pass them.

Single permission/role

In simplest cases you allow users having single role permission to access the state. To achieve that you can pass as String desired role/permission to only/except property:

$stateProvider
  .state('dashboard', {
    [...]
    data: {
      permissions: {
        only: 'isAuthorized'
      }
    }
  });

In given case when user is trying to access dashboard state PermStateAuthorization service is called checking if isAuthorized permission is valid looking through PermPermissionStore and PermRoleStore for it's definition:

  • if permission definition is not found it stops transition
  • if permission definition is found but validationFunction returns false or rejected promise it stops transition
  • if permission definition is found and validationFunction returns true or resolved promise, meaning that user is authorized to access the state transition proceeds to the state

Multiple permissions/roles

Often several permissions/roles are sufficient to allow/deny user to access the state. Then array value comes in handy:

$stateProvider
  .state('userManagement', {
    [...]
    data: {
      permissions: {
        only: ['ADMIN','MODERATOR']
      }
    }
  });

When PermStateAuthorization service will be called it would expect user to have either ADMIN or MODERATOR roles to pass him to userManagement state.

💡 Note
Between values in array operator OR is used to create alternative. If you need AND operator between permissions define additional PermRole containing set of those.

Dynamic access

You can find states that would require to verify access dynamically - often depending on parameters.

Let's imagine situation where user want to modify the invoice. We need to check every time if he is allowed to do that on state level. We are gonna use PermTransitionProperties object to check whether he is able to do that.

$stateProvider
  .state('invoices/:id/:isEditable', {
    [...]
    data: {
      permissions: {
        only: function(transitionProperties){
          if(transitionProperties.toParams.isEditable){
            return ['canEdit'];
          } else {
            return ['canRead'];
          }
        }
      }
    }
  });

So whenever we try access state with param isEditable set to true additional check for permission canEdit will be made. Otherwise only canRead will be required.

🔥 Important
Notice that function require to always return array of roles/permissions in order to work properly.

Property redirectTo

Property redirectTo:

  • instructs PermStateAuthorization service how to handle unauthorized access
  • when used as String defines single redirection rule
  • when used as Objects defines single/multiple redirection rules
  • when used as Function defines dynamic redirection rule(s)

Single redirection rule

In case you want to redirect to some specific state when user is not authorized pass to redirectTo name of that state.

$stateProvider
  .state('dashboard', {
    [...]
    data: {
      permissions: {
        except: ['anonymous'],
        redirectTo: 'login'
      }
    }
  });

In order to pass additional properties like params use pass pass redirectTo as object.

$stateProvider
  .state('dashboard', {
    [...]
    data: {
      permissions: {
        except: ['anonymous'],
        redirectTo: {
          state: 'login',
          params: {
            token: 'notSecret'
            isAuthorized: 'true'
          }
        }
      }
    }
  });

💡 Note
When the state to which user will be redirected is not defined note that he will be intercepted be general $urlRouterProvider.otherwise() rule.

Multiple redirection rules

In some situation you want to redirect user based on denied permission/role to create redirection strategies. In order to do that you have to create redirection Object that contain keys representing rejected permissions or roles and values implementing redirection rules.

Redirection rules are represented by following values:

Value type Return Usage
String [String] Simple state transitions
Object [Object] Redirection with custom parameters or options
Function [String|Object] Dynamic properties-based redirection

💡 Note
Use default property that will handle fallback redirect for not defined permissions.

The simplest example of multiple redirection rules are redirection based on pairs role/permission and state. When user is not granted to access the state will be redirected to agendaList if missing canReadAgenda permission or to dashboard when missing canEditAgenda. Property default is reserved for cases when you want handle specific cases leaving default redirection.

$stateProvider
  .state('agenda', {
    [...]
    data: {
      permissions: {
        only: ['canReadAgenda','canEditAgenda'],
        redirectTo: {
          canReadAgenda: 'agendaList',
          canEditAgenda: 'dashboard',
          default: 'login'
        }
      }
    }
  })

If you need more control over redirection parameters Object as a value can be used to customise target state params and transition options.

$stateProvider
  .state('agenda', {
    [...]
    data: {
      permissions: {
        only: ['canEditAgenda'],
        redirectTo: 
          canEditAgenda: {
            state: 'dashboard',
            params: {
              paramOne: 'one'
              paramTwo: 'two'
            },
            options: {
              location: false
              reload: true
            }
          },
          default: 'login'
        }
      }
    }
  })

To present usage redirectTo as Object with values as Function in a state definition agenda presented below redirection rules are interpreted as:

  • when user does not have canReadAgenda invoked function returns string representing the state name to which unauthorized user will be redirected
  • when user does not have canEditAgenda invoked function returns object with custom options and params that will be passed along to transited dashboard state
$stateProvider
  .state('agenda', {
    [...]
    data: {
      permissions: {
        only: ['canReadAgenda','canEditAgenda'],
        redirectTo: {
          canReadAgenda: function(rejectedPermission, transitionProperties){
            return 'dashboard';
          },
          canEditAgenda: function(rejectedPermission, transitionProperties){
            return {
              state: 'dashboard',
              params: {
                paramOne: 'one'
              }
            };
          },
          default: 'login'
        }
      }
    }
  })

Dynamic redirection rules

Similarly to examples showing defining dynamic access to state redirection can also be defined based on any PermTransitionProperties allowing to heavily customize behaviour of the state redirection.

💡 Note
Remember to always return from function state name or object. Otherwise errors will thrown from either angular-permission or ui-router library.

$stateProvider
  .state('agenda/:isEditable', {
    [...]
    data: {
      permissions: {
        only: ['canReadAgenda','canEditAgenda'],
        redirectTo: function(rejectedPermission, transitionProperties){
          if(transitionProperties.toParams.isEditable){
            return 'login';
          } else {
            return {
              state: 'dashboard',
              params: {
                paramOne: 'one'
              }
            };
          }
        }
      }
    }
  })

You may notice that when using functions inside state definition objects your module get quite big and nasty. But the sky is the limit! Use angular Constant pattern to extract calling to those methods and clean up your code.

$stateProvider
  .state('agenda/:isEditable', {
    [...]
    data: {
      permissions: {
        only: ['canReadAgenda','canEditAgenda'],
        redirectTo: AuthorizationMethods.redirectionAgenda
      }
    }
  })

State permission inheritance

Thanks to tree structure of ui-router states inside router all states permissions are inherited down the state tree. So there is no need to repeat yourself including the same permissions/roles in child states. They will be included in state authorization processes. Let's see how it works:

$stateProvider
  .state('agendas', {
    url: '/agendas',
    [...]
    data: {
      permissions: {
        only: ['canReadAgenda', 'MODERATOR']
      }
    }
  })
  .state('agendas.edit', {
    url: '/agendas/edit/:id',
    [...]
    data: {
      permissions: {
        only: ['canEditAgenda']
      }
    }
  })

When user will try to edit one of agendas PermStateAuthorization service will check for required permissions building expression that concatenates parent permissions with AND operator. So service try resolve the following statement [canReadAgenda OR MODERATOR] AND [canEditAgenda] and if it's true will pass the user to the agendas.edit state.

State redirection inheritance

Inheritance of property redirectTo in child states works in exact similar manner as for permission only and except properties. For a given example:

$stateProvider
  .state('agendas', {
    url: '/agendas',
    [...]
    data: {
      permissions: {
        only: ['canReadAgenda'],
        redirectTo: {
          canReadAgenda: 'dashboard',
          default: 'login'
        }
      }
    }
  })
  .state('agendas.edit', {
    url: '/agendas/edit/:id',
    [...]
    data: {
      permissions: {
        only: ['canEditAgenda'],
        redirectTo: {
           canEditAgenda: 'agendas',
           default: 'login'
         }
      }
    }
  })

As an outcome if user that does not have permission canReadAgenda will try to access state agendas.edit will be redirect to inherited from parent state rule state dashboard. So you will see that agendas.edit redirection rules are actually applied like that:

$stateProvider
  .state('agendas.edit', {
    url: '/agendas/edit/:id',
    [...]
    data: {
      permissions: {
        only: ['canEditAgenda'],
        redirectTo: {
           canReadAgenda: 'dashboard',
           canEditAgenda: 'agendas',
           default: 'login'
         }
      }
    }
  })

💡 Note
Using redirectTo as string or function returning string will overwrite only default redirection rule and if any parent state have more specific rules applied they will take precedence over that rule.


Next to read: 👉 Emitted events