I have the following BaseApiController:
public class BaseApiController : ApiController
{
public readonly Current _current { get; private set; }
}
All my ApiControllers inherit from this one.
I need to do a validation in all of my methods inside my ApiControllers, that checks if the userId passed match the current HttpContext.Current.User.Identity.
[Route("{userId}/cars")]
public HttpResponseMessage GetCars(int userId)
{
if (userId != _current.UserId)
{
return Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Unauthorized");
}
}
Is there anyway to do it on the BaseApiController, so that I can avoid doing this validation on all of the endpoints that receive the userId as argument?
You can create custom validation attribute based on ActionFilterAttribute to achieve what you need.
For your case it can look like this:
public class UserAccessCheckAttribute: ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var controller = actionContext.ControllerContext.Controller as BaseApiController;
object requestUserIdObj;
if (controller != null && actionContext.ActionArguments.TryGetValue("userId", out requestUserIdObj))
{
var userId = (int) requestUserIdObj;
if (userId != controller._current.UserId)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Unauthorized");
}
}
}
}
After that you can decorate either controllers or specific actions where you need to perform user id check with this attribute:
[Authorize]
[RoutePrefix("api/Account")]
[UserAccessCheck] //check user id for all actions in controller
public class AccountController : BaseApiController
{
//....
}
public class ValuesController : BaseApiController
{
//....
[UserAccessCheck] //check user id for specific action only
public IEnumerable<string> Get()
{
//...
}
}
After that no additional code in your actions required
Related
[CheckAccessMethod(RouteData.Values["action"].ToString())]
[HttpGet]
public IActionResult Get(){...}
class CheckAccessMethodAttribute : Attribute
{
string MethodName { get; set; }
public CheckAccessMethodAttribute(string methodName)
{
MethodName = methodName;
}
}
I can’t get the current request route.
I want to create method access logic for users
One option would be to use the ActionFilterAttribute, then you'd have access to the ResultExecutingContext which has the route data you need. More information here
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var action = context.RouteData.Values["action"];
//do something with action here
base.OnResultExecuting(context);
}
}
I write this code in several places and always repeat this logic:
public ActionResult MyMethod(MyModel collection)
{
if (!ModelState.IsValid)
{
return Json(false);//to read it from javascript, it's always equal
}
else
{
try
{
//logic here
return Json(true);//or Json(false);
}
catch
{
return Json(false);//to read it from javascript, it's always equal
}
}
}
Is there any way using action filters, not to be repeating the try-catch, ask if the model is valid and return Json(false) as ActionResult?
To conform with REST, you should return http bad request 400 to indicate that the request is malformed (model is invalid) instead of returning Json(false).
Try this attribute from asp.net official site for web api:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
A version for asp.net mvc could be like this:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.Controller.ViewData.ModelState.IsValid)
{
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
}
If you want to do this in MVC6 or Mvc Core and without specifying your attribute on all of your Action methods then this is how you do it.
First create your ActionFilter
public class ModelStateValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting( ActionExecutingContext context )
{
if ( context.HttpContext.Request.Method == "POST" && !context.ModelState.IsValid )
context.Result = new BadRequestObjectResult( context.ModelState );
}
}
Now create a convention in which you will apply this ActionFilter to all of your controllers.
public class ModelStateValidatorConvension : IApplicationModelConvention
{
public void Apply( ApplicationModel application )
{
foreach ( var controllerModel in application.Controllers )
{
controllerModel.Filters.Add( new ModelStateValidationFilterAttribute() );
}
}
}
And the last thing is to register this convention in MVC
public void ConfigureServices( IServiceCollection services )
{
services.Configure<MvcOptions>( x => x.Conventions.Add( new ModelStateValidatorConvension() ) );
}
Starting from ASP.Net Core 2.1, the [ApiController] attribute will trigger automatic model validation:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
Consequently, the following code is unnecessary in action methods or custom ActionFilterAttribute:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Source: https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.1#automatic-http-400-responses-1
Here is how to use the code from Khanh TO (from asp.net official site):
To apply this filter to all Web API controllers, add an instance of the filter to the HttpConfiguration.Filters collection during configuration:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new ValidateModelAttribute());
// ...
}
}
Another option is to set the filter as an attribute on individual controllers or controller actions:
public class ProductsController : ApiController
{
[ValidateModel]
public HttpResponseMessage Post(Product product)
{
// ...
}
}
public class ValidateModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new ViewResult();
}
}
}
I have the following requirement to implement the Access Control list
public class SecurityObject{
public string Key{get;set;}
public string DisplayName{get;set;}
public bool isAllowed{get;set;}
}
public class Role{
List<SecurityObject> AccessibleObjects{get;set;}
}
Currently I use forms authentication for basic authorization. Below is my code
Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
public override void Init()
{
this.PostAuthenticateRequest += new
EventHandler(MvcApplication_PostAuthenticateRequest);
base.Init();
}
void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
HttpCookie authCookie =
HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
string encTicket = authCookie.Value;
if (!String.IsNullOrEmpty(encTicket))
{
FormsAuthenticationTicket ticket =
FormsAuthentication.Decrypt(encTicket);
string[] userData = ticket.UserData.Split(new string[] { "___" },
StringSplitOptions.None);
string[] roles = null;
if (userData.Length > 1)
{
roles = userData[1].Split(',');
}
MyCustomIdentity identity = new MyCustomIdentity(ticket);
GenericPrincipal principle = new GenericPrincipal(identity, roles);
HttpContext.Current.User = principle;
}
}
}}
My current controller class
public class AdminController : Controller
{
[HttpPost, Authorize, ValidateAntiForgeryToken]
public ActionResult SaveUser(UserDetailViewModel viewModel)
{
}
}
My Target controller class
public class AdminController : Controller
{
[HttpPost, Authorize(ACLKey="USR_SAVE"), ValidateAntiForgeryToken]
public ActionResult SaveUser(UserDetailViewModel viewModel)
{
}
}
I want my action method to be decorated with ACLKey and I would like to check whether the User Role has the given key and based on that I need to execute or return HttpUnauthorizedResult page, even for Ajax requests from jQuery.
I referred many like Customizing authorization in ASP.NET MVC But i didnt find a way to execute both forms authentication and my custom ACLKey check.
How do i parse the value USR_SAVE and process custom authentication using CustomAuthorizeFilter?
You can try like this
public class FeatureAuthenticationAttribute : FilterAttribute, IAuthorizationFilter
{
public string AllowFeature { get; set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
var filterAttribute = filterContext.ActionDescriptor.GetFilterAttributes(true)
.Where(a => a.GetType() ==
typeof(FeatureAuthenticationAttribute));
if (filterAttribute != null)
{
foreach (FeatureAuthenticationAttribute attr in filterAttribute)
{
AllowFeature = attr.AllowFeature;
}
List<Role> roles =
((User)filterContext.HttpContext.Session["CurrentUser"]).Roles;
bool allowed = SecurityHelper.IsAccessible(AllowFeature, roles);
if (!allowed)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
}
In you action method
[FeatureAuthentication(AllowFeature="USR_SAVE")]
public ActionResult Index()
{
}
Hope this will help you!
You can use a filter attribute:
public class ACLCheckAttribute : FilterAttribute, IActionFilter
In OnActionExecuting, you can grab USR_SAVE. Without knowing where it comes from, I would assume that it comes from:
The Form: you can grab any form values from the context passed into ONActionExecuting, by navigating to the HttpContext.Request.Form collection
Session, etc.: HttpContext would also have these.
The action method: From an attribute, using the context passed in for the action, it has a list of ActionParameters that can be accessed like a dictionary, allowing you to check and extract your value
If somewhere else, please comment where. You can apply this attribute to a controller or method, or globally set it by adding it to the globalfilters collection (GlobalFilters.Filters.Add()), or in the FilterConfig file in the App_Start folder.
I have this repetitive code in all the controllers:
var user = Session["_User"] as User;
if (user== null)
{
return RedirectToAction("Index");
}
How can I refactor this? should i add this to an attribute or make a Session Wrapper?
What would be the best approach?
extend your all controllers with a MasterController and override OnActionExecuting method as follows
public class MasterController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var user = Session["_User"] as User;
if (user== null)
{
if(filterContext.ActionDescriptor.ActionName != "Index" || filterContext.ActionDescriptor.ControllerDescriptor.ControllerName != "ControllerThatContainIndexAction")
{
filterContext.Result = this.RedirectToAction("Index");
return;
}
}
base.OnActionExecuting(filterContext);
}
}
Description
There are many ways. I would create a own Controller.
Than you inherit the other controllers from this.
Sample
public class BaseController : Controller
{
public User User { get; private set; }
public BaseController()
{
this.User = Session["_User"] as User;
}
}
public class HomeController: BaseController
{
// your action methods
}
I'm using an ActionFilterAttribute to do custom authentication logic. The Attribute will only be used on a derived Controller class that contains my authentication logic.
Here's my Controller, derived from my custom controller class, and a sample attribute:
public class MyController : CustomControllerBase
{
[CustomAuthorize(UserType = UserTypes.Admin)]
public ActionResult DoSomethingSecure()
{
return View();
}
}
Here's an example of my ActionFilterAttribute:
public class CustomAuthorizeAttribute : ActionFilterAttribute
{
public MyUserTypes UserType { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
myUser user = ((CustomControllerBase)filterContext.Controller).User;
if(!user.isAuthenticated)
{
filterContext.RequestContext.HttpContext.Response.StatusCode = 401;
}
}
}
Works great.
Here's the question: Can I demand that this attribute ONLY be used on Actions in my custom controller type?
You can put the ActionFilter on the class itself. All actions in the class will realize the ActionFilter.
[CustomAuthorize]
public class AuthorizedControllerBase : CustomControllerBase
{
}
public class OpenAccessControllerBase : CustomControllerBase
{
}
public class MyRealController : AuthorizedControllerBase
{
// GET: /myrealcontroller/index
public ActionResult Index()
{
return View();
}
}
Based on the comments and the constraints of my system, I took a hybrid approach. Basically, if the request comes through via a cached route or the "User" is not set for any reason, authentication fails in the proper way.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private MyUser User { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
//Lazy loads the user in the controller.
User = ((MyControllerBase)filterContext.Controller).User;
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isAuthorized = false;
string retLink = httpContext.Request.Url.AbsolutePath;
if(User != null)
{
isAuthorized = User.IsValidated;
}
if (!isAuthorized)
{
//If the current request is coming in via an AJAX call,
//simply return a basic 401 status code, otherwise,
//redirect to the login page.
if (httpContext.Request.IsAjaxRequest())
{
httpContext.Response.StatusCode = 401;
}
else
{
httpContext.Response.Redirect("/login?retlink=" + retLink);
}
}
return isAuthorized;
}
}