I'm looking for a way to conditionally enforce the [Authorize] attribute on API Controller methods based on the route they are accessed from.
In my software, I have pages that can be accessed one of two ways, as an authorised user with full access or as an anonymous user in "viewer mode", with limited functionality.
Both of these require access to the same API methods. I want to keep the "full" version of these calls as secure as possible, while the anonymous versions should be unimpeded. To give you an example of such a method before the "viewer mode" existed:
[Route("{outerId:int}/{innerId:int}/Action")]
public virtual HttpResponseMessage PutAction(int outerId, int innerId)
{
...
}
The outerId and innerId are essentially indicators of subdirectories containing the relevant data. I should also point out that the controller has the [Authorize] attribute.
Viewers access deep copies of this data through a different path that only requires one parameter, so my current solution is to anonymise the method and have 2 routes to differentiate the two different access types, authenticating within the method where appropriate:
[AllowAnonymous]
[Route("{id:int}/{innerId:int}/Action", Order = 1)]
[Route("{id:int}/Viewer/Action", Order = 2)]
public virtual HttpResponseMessage PutAction(int id, int? innerId = null)
{
if (innerId != null) // check userId using User.Identity.GetUserId()
...
}
This works well most of the time, but unfortunately these methods will occasionally fail when accessed by full users, as User == null. The Authorize attribute is the only thing I know that would prevent this from happening, but is there a way to only enforce this attribute for different routes?
Related
I have the following method in my controller, using C# MVC with Attribute roouting:
[Route("")]
[Route("/something-else")]
public IActionResult Index(){
}
Im using two different routes to access this functionallity, since i want customers with bookmarks to the previous implementation to work.
The problem is that i cant specify which of these Routes will be the default one when i issue the action like this:
<a asp-controller="FOO" asp-action="Index">
Everythiing works as expected, both URLs work, but i cant specify which of these routes will be used when navigated to by the action, via the action above.
I would like that the first route be used everytime i navigate to this action, except when someone explicitly writes the old url into the browser.
Are there any default attributes to the [Route("")] tag?
The RouteAttribute class has an Order property. From the docs:
Gets the route order. The order determines the order of route execution. Routes with a lower order value are tried first.
For example:
[Route("/something-else", Order = 1)]
[Route("", Order = 2)]
public IActionResult Index(){
}
As an aside, I would strongly discourage you from serving the same page with multiple URLs. Google's indexing will give you worse ranking because of it. Instead, consider returning a redirect to the new URL instead.
I am making a small SAAS application. I am using the same database for all users and only differentiating between the data using username and ids. This means that the user can type in a new url in the browser and see other users data. This, of course is not a desirable approach. I would like to do a check to see if the current user can actually access the resources: eg.
http://myapplication.com/images/15
And if the user changes the url to
http://myapplication.com/images/16
I should do a check in my database to see if the current user actually has access to see the images with user id 16. And if not redirect to a "not authorized" page.
How do I implement this?
The first step is to make sure that you never have any ID's for the user itself in the url. For instance, never have http://example.com/?user=10. You should always get the users id from their authentication rather than from the URL (or posted values either).
The second step, is to use that ID in your queries. So, for instance, let's say they seek http://example.com/images/100, then in your database you should have a mechanism that links the asset's ownership to the user, either a userid or a mapping table of id's to asset's, etc.. This way, if the user isn't allowed access, it will just return an empty result set. It's impossible for the data to be returned, and the empty result set should tell your page that the item doesn't exist (not necessarily an authorization failure, just that the object doesn't exist).
Third, any pages which are inherently about the user, such as a user profile, account page, or dashboard should never have any ID's at all in the URL, it should just automatically go to the authenticated users page.
Finally, if you need to prevent the user from accessing an entire page or set of pages, then you should do this in the OnAuthorization event or similar (custom attribute, base class, etc..) or using the built-in attribute authorization and use role based authorization. Never do authorization in the PageLoad or similar event (such as the controller action), because by the time you get to that step a lot of work has already happened in the pipeline. It's best to block access long before the page even starts to setup. Authorization events happen at the very beginning of the pipeline.
Make an Action that check userId and returns error page or file
public FileResult Image(string imageName)
{
string UserId = MethodWhereYouGetCurrentUserID();
if(imageName == null) return View("~/Views/Shared/Error.cshtml", (object)"Null image");
string imageShortName = imageName.Split(".")[0];
if(!UserId == imageShortName) return View(~/Views/Shared/Error.cshtml, (object)"You can't access to this");
string path = Server.MapPath("~/Contant/images/"+imageName);
return File(path, "image/jpg");
}
RouteConfig file
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute
(
name: "ImageRoute",
url: "/images/imageName",
default: new {controller = "Home", action = "GetImage"}
);
}
I'm writing in C# for ASP.NET Web API 2. What I want is a catch-all method that will execute for every single request that comes to my Web API.
If the method returns null, then the original routing should continue, seeking out the correct method. However, if the method returns, say, an HTTPResponseMessage, the server should return that response and not proceed on to normal routing.
The use case would be the ability to handle various scenarios that may impact the entire API. For example: ban a single IP address, block (or whitelist) certain user agents, deal with API call counting (e.g. someone can only make X requests to any API method in Y minutes).
The only way I can imagine to do this right now is to literally include a method call in each and every new method I write for my API. For example,
[HttpGet]
public HttpResponseMessage myNewMethod()
{
// I want to avoid having to do this in every single method.
var check = methodThatEitherReturnsResponseOrNull(Request);
if (check != null) return (HttpResponseMessage)check;
// The method returned null so we go ahead with normal processing.
...
}
Is there some way to accomplish this in routing?
This is what Action Filters are for. These are Attributes that you can place either globally, at the class (Controller), or at the method (Action) levels. These attributes can do preprocessing where you execute some code before your action executes or post processing where you execute code after the action executes.
When using pre processing you have the option to return a result to the caller and not have your method (action) be fired at all. This is good for model validation, authorization checks, etc.
To register a filter globally edit the WebApiConfig.cs file.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new YourFilterAttribute()); // add record
// rest of code
}
}
To create a custom attribute inherit from System.Web.Http.Filters.ActionFilterAttribute or you can implement interface System.Web.Http.Filters.IActionFilter or you can implement IAuthorizationFilter/AuthorizationFilterAttribute if you specifically want to allow/deny a request.
It also sounds like you want to create multiple attributes, one for each role like IP filtering or count calling etc. That way it would be more modular instead of one enormous authorization filter.
There are many tutorials out there like this one (chosen at random in my Google search results). I am not going to post code because you did not do so either so I would just be guessing as to what you wanted to do.
I have an ASP.Net MVC site, which connects to a web service.
The site's view model contains objects for each group of required service data AccountDetails (containing AccountId, AccountType, etc.), ContactDetails (containing Name, Address, etc.) and so on.
The service has a 'CreateUser()' method that accepts these objects as parameters, and it then performs all the validation itself - handing back an Object which has an array of any errors that have been found, including the name of the specific property/field.
I would like to know if there is a way of passing this returned error data into either DataAnnotations or something else.
I specifically can't write the conditions in the model itself, because the validation conditions within the web service are open to change at any moment - and we want this to dictate what fails and what succeeds.
== FURTHER INFO FOR MAKE IT A BIT CLEARER ==
Imagine I were locally (within the View Model) creating the ContactDetails class, I could very simply do this
public class ContactDetails
{
[IsRequired()]
[CustomAttributeofSomekind]
public string FirstName { get; set; }
public string LastName { get; set; }
}
However in this scenario - if we wanted to change the validation critera for whatever reason we would have to change it in both the web service AND in all the client websites that access the service.
We don't want to have to do this - instead I if (in the above) scenario ContactDetails.LastName is suddenly required and must be no more than 10 characters - this should only need updating in the web service.
I think you have two options:
Create a User class to wrap the CreateUser() method and add the DataAnnotations to that (this is what I would do, it allows you to go strongly-typed.)
Call the CreateUser() method directly from the controller Action and use server-side validation. Add each validation error in the CreateUser() result to the ModelState.Errors collection when any validation rules are violated.
I have an ASP.NET MVC application with an Admin area that deals with administering Companies and their child entities, such as Users and Products. The default route associated with a child entity is defined as follows:
"Admin/Company/{companyID}/{controller}/{id}/{action}"
I would like to ensure that, everywhere in the Admin area, whenever the incoming route includes companyID, that this value is automatically included in every generated URL. For example, if my User Edit page has a link defined with Html.ActionLink("back to list", "Index"), the routing system will automatically grab the companyID from the incoming route data and include it in the outgoing route, without having to explicitly specify it in the call to ActionLink.
I think there's more than one way to achieve this, but is there a preferred/best way? Does it scream for a custom route handler? Something else?
My goal is to not lose the current company context when navigating around in the sub-sections, and I don't want to use Session - that could burn me if the user opens up multiple companies in different browser windows/tabs.
Thanks in advance!
Todd,
I am using an ActionFilterAttribute in my MVC 2 application to make this happen. There may be better ways to do this:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
sealed class MyContextProviderAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// See if the context is provided so that you can cache it.
string myContextParam = filterContext.HttpContext.Request["myContextParam"] ;
if (!string.IsNullOrEmpty(myContextParam))
filterContext.Controller.TempData["myContextParam"] = myContextParam;
else
// Manipulate the action parameters and use the cached value.
if (filterContext.ActionParameters.Keys.Contains("myContextParam"))
filterContext.ActionParameters["myContextParam"] = filterContext.Controller.TempData["myContextParam"];
else
filterContext.ActionParameters.Add("myContextParam", filterContext.Controller.TempData["myContextParam"]);
base.OnActionExecuting(filterContext);
}
}