I'm not happy with the Authorize attribute which is too simple and difficult to scale
[Authorize(Users = "Admin, Manager", Roles = "Manager")]
The roles seem to be fixed and require source code change to add more role: what if client requests you to add new role? You will need to define new one, and add to the relevant controller/action.
Active Role Engine aims at a flexible authorization using permission-based approach.
- Controller action has its own permission
- Role is defined base on such permission
- User permission can override role permission
So that role can be flexibly defined and scaled: developer and client are decoupled. The only thing needs to be agreed at the beginning is how the permission should be defined.
Permission-based access control is not something new, it has existed for decades.
- Permission can be defined for each controller
- Permission can be defined for each controller action
- Permission is inheritable:
- Controller action can inherit permission from its controller
- Controller can inherit permission from its base class
- Role is a group of permissions
- User may have many roles
- User inherit permission from roles
- User can override the role permission
- SysUser: store user information
- SysPermission: store permissions (optional)
- SysRole: store role information
- SysUserRole: store user's role
- SysRolePermission: store role's permission
- SysUserPermission: override role's permission. Is Add
- If true: always add the permission to user,
- if false: always remove the permission from user
With Active Role Engine, defining permission is easy like this
[ActivePermission]
[ActivePermission(Permission = "CustomPermission", Description = "Custom Permission")]
In the most simple case, you just need to decorate your base controller with [ActivePermission]
: by this way, each inherited controller will require a permission which is the controller name.
In the most complicated case, you can decorate [ActivePermission]
to each controller action which allow you to control every single action.
- You can found the script to create database in
scripts
folder. You can freely select your persistence solution - Decorate controller or controller action with
ActivePermission
attribute- By default, the PermissionId is combination of Area (if any), the controller name and the controller action's name (if any): [Area/]Controller[/Action]
- You can override the default PermissionId by providing your own
- The option
Group
andDescription
is used for displaying purpose only PermissionType
can be:- Permisson (default): user require specific permission to access
- SuperAdmin: only super admin can access the feature
- Authenticated: all authenticated users can access
- The entry point is
AuthConfig.ConfigRoleEngine
:RestoreUserSession
: currently the user permission is stored in Session which has a limitation: session timeout. Hence,RestoreUserSession
is neccessary for the engine to restore the user's permissionHandleUnauthorizedRequest
(optional): how system handle the unauthorized request. If not provided, the engine will return 403 Forbidden
- The engine does require your extra work to manage user, role, permission which is the responsibility of the application itself
Entire sample is provided in SampleWeb application for your reference. The data is stored in memory hence it will be reset next time you start the application.
Super Admin
[ActivePermission(PermissionType = PermissionType.SuperAdmin, Group = "Super Admin", Description = "This controller can be accessed by supper admin only")]
public class SuperAdminController : ActiveControllerBase
{
[ActivePermission(PermissionType = PermissionType.SuperAdmin, Description = "This action can be access by super admin only")]
public ActionResult Index()
{
return AuthorizedContent();
}
// inherit permission from controller
public ActionResult Default()
{
return AuthorizedContent();
}
}
Inherit
[ActivePermission(Group = "Controller Permission", Description = "All actions in the controller have the same permission")]
public class ControllerPermissionController : ActiveControllerBase
{
// inherit permission from controller
public ActionResult Index()
{
return AuthorizedContent();
}
// inherit permission from controller
public ActionResult Action1()
{
return AuthorizedContent();
}
// inherit permission from controller
public ActionResult Action2()
{
return AuthorizedContent();
}
[AllowAnonymous]
public ActionResult AllowAnonymous()
{
return AuthorizedContent();
}
}
Action Permission
[ActivePermission(Group = "Action Permission", Description = "Each action has its own permission")]
public class ActionPermissionController : ActiveControllerBase
{
[ActivePermission]
public ActionResult Index()
{
return AuthorizedContent();
}
[ActivePermission]
public ActionResult Action1()
{
return AuthorizedContent();
}
[ActivePermission]
public ActionResult Action2()
{
return AuthorizedContent();
}
[ActivePermission(Permission = "CustomPermissionId", Description = "Action3: PermissionId can be customized")]
public ActionResult Action3()
{
return AuthorizedContent();
}
[AllowAnonymous]
public ActionResult AllowAnonymous()
{
return AuthorizedContent();
}
- You can skip the authorization with
AllowAnonymousAttribute
orNonActionAttribute
.ChildAction
is also skipped - The sample is using OWIN to simplify the user principle management, you can replace with your own
The first version is developed using ASP.NET MVC5 and will be developed for .NET Core soon.
I'm looking for contributors to ship the solution to Java, PHP, NodeJs, Go, .. or recommend the existing solution so that I will put the reference here.