I highly recommend separate your project as layers and have a Service Layer that can be used by your many projects such as MVC and/or Background Jobs and you can add this Authorization Service to Service Layer. So you can call this Authorization Service Layer.
This way, you don't need to extend Authorization to the Model layer because your other entry-points can call this layer. Besides, Models is not responsible about Authentication. It is worse than being on Controllers.
Additionally, there are kind of authorization such as Role-Right Management. These authorizations can be for per your business logic. If so, you should also call required validation method to check this authorization from your business logic layer. By doing this, even you don't check if users authorize(especially from background jobs), they can not perform this action if not authorized.
public class TaskService
{
private readonly AuthorizationService _authorizationService;
public TaskService(AuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public void AssingTask(int taskId, int userId)
{
// you can perform this validation by attributes that properly generated for your design.
// It is just sample
// by this validate there is no user can do this action without having required right(s).
this.Validate(Rights.AssignTask, userId);
// do your assign business.
}
private void Validate(Rights right, int userId)
{
if (!_authorizationService.IsUserAuthorize(right, userId))
throw new Exception("User has not right for this action.");
}
}
Next step is better way to use this authorization methods on your monolith MVC layer. Again I highly recommend, create static extension methods and call it throughout the project for authorization. You can call this static extension methods by your User object from view as:
in view:
@if(User.IsAuthorize("Something"))
{
<submit type = "button" text ="you can do this" />
}
and controller:
public class TaskController : Controller
{
[CustomAuthorize(Right = Rights.AssignTask)]
public ActionResult TaskAssing()
{
_taskService.AssignTask(userId);
}
}
CustomAuthorize
attribute:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class CustomAuthorize : // ...
{
public Rights Right { get; set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
if (!AuthorizeExtension.IsAuthorize(filterContext.HttpContext.User.Identity, Right))
{
redirect("UnAutorize");
}
}
}
static extension method:
public static class AuthorizeExtension
{
public static bool IsAuthorize(this IPrincipal principal, Rights right)
{
return _authorizationService.IsUserAuthorize(right, principal.Identity.Id);
}
}
Last, what makes users are authorized? It's totally up to you and your design. But, there are two option I can suggest:
1. Role-Right Management
You can define a right for each business function. For example Task Create, Task Assign, Task Delete, Start Task, Stop Task, User Create, User Update, Prepare Report, Report Details ... Should defined all rights for all business functions. Then let role can have multiple rights.((n)Role ->(n)Right). Then, create User-Role relations. It's also (n)User->(n)Role relation. You need to store all these relations to database.
This design, let you manage authorization for all business function by adding rights to role and assigning roles to users. In your IsUserAuthorize
method in Authorization Service Layer, you can check if user has required right by his/ her role(s).
2. Minimum Type Of Authorized Users
You can determine minimum type of authorized users and determine what they are able to do. You can define all things as enum
and can determine which user is which type by code.
public enum Roles
{
AdminRole,
StandartUser,
NewOne,
Guest
}
public enum Services
{
Task,
Report,
User
//...
}
public class AuthorizationService
{
public bool IsUserAuthorize(int userId, Services service)
{
Roles role = DetermineUserRole(userId);
switch(role)
{
case Roles.AdminRole:
return true;
case Roles.Guest:
return service == Services.Report ;
// ...
default:
throw new Exception("Can not determine user role");
}
}
}
Which one you should choose?
First option is really good practice and you can manage all functions. Also, you can let users to create new role and assign to others.
But, as you mentioned, your project monolith and probably there will be no major change or add new functionality. Thus, it seems better to choose second one. So, you won't need to define right for all business functions and store those rights and relations to database.
when the Controller isn't the only way to interact with the models
Why? Why these other interactions cannot be forwarded to controllers? You can have more controllers besides those specifically dedicated to the MVC.