URL rewrite in OWIN middleware - c#

I have a problem with URL rewriting which works in Global.asax but not in OWIN middleware.
Global.asax code
protected void Application_BeginRequest()
{
//Perfectly working rewrite.
//By route rules, this resolves to the action Global()
//of the HomeController
HttpContext.Current.RewritePath("Home/Global");
}
OWIN middleware code (used for culture detection, code shortened for brevity)
public class GlobalizationMiddleware : OwinMiddleware
{
public GlobalizationMiddleware(OwinMiddleware next)
: base(next)
{ }
public async override Task Invoke(IOwinContext context)
{
context.Request.Path = new PathString("/Home/Global");
await Next.Invoke(context);
}
}
I expect that "Global" action of the controller "Home" gets called...but instead, the default action "Index" is called.
After the Path is changed context.Request.Uri.AbsoluteUri is http://localhost/Global/Home
But Controller's Request.Url.AbsoluteUri is still http://localhost
I even tried context.Environment["owin.RequestPath"] = "/Home/Global"; but that doesn's seem to work either.
Before anyone asks, yes, I call the IAppBuilder.Use(typeof(GlobalizationMiddleware)) in Startup.cs and the debugger enters the Invoke method.
What am I doing wrong?
EDIT
I even tried referencing System.Web and then doing this...doesn't work either :(
System.Web.Routing.RequestContext requestContext = context.Environment["System.Web.Routing.RequestContext"] as System.Web.Routing.RequestContext;
requestContext.HttpContext.RewritePath("/Home/Global");
System.Web.HttpContextBase contextBase = context.Environment["System.Web.HttpContextBase"] as System.Web.HttpContextBase;
contextBase.RewritePath("/Home/Global");
EDIT 2 - Found a working solution (see below) but I'm unsure whether it's the right solution, comments would be appreciated :)

I found a working solution.
Unfortunately, I needed to include System.Web. I'm directly altering the RouteData object in the RequestContext.
System.Web.Routing.RequestContext requestContext = context.Environment["System.Web.Routing.RequestContext"] as System.Web.Routing.RequestContext;
requestContext.HttpContext.RewritePath("Home/Global");
requestContext.RouteData.Values["action"] = "Global";
But this feels too hacky to my tastes... I'm not sure if this is the right solution so I won't accept this as the valid answer, maybe someone will come with a better solution.

I ran into this as well, and found that one way to get this working is just to use HttpContext
HttpContext.Current.RewritePath("/new/path");
If you're already including System.Web anyway this seems to be a workable solution.
OWIN is a layer on top of ASP.NET (System.Web), and some of the ASP.NET components (such as MVC) are not built on top of OWIN but actually ASP.NET directly.
Looking deeper, OwinRequest doesn't have any reference to System.Web.HttpContext and instead just deals with Environment (source), so that's why setting the OWIN request path doesn't affect ASP.NET apps.

Related

Blocking anonymous access by default in ASP .NET 5

My team and I are starting up a new website project in ASP .NET 5 and I'm trying to set up the basis of our user authentication and authorization policy.
So far, I've managed to use the [Authorize] and [AllowAnonymous] attributes to selectively define an authorization policy controllers or actions. The one thing I'm still struggling to achieve is defining a default authorization policy.
Bascially, I'd like every controller and action to behave as if they had an [Authorize] attribute by default, so that only actions specifically tagged as [AllowAnonymous] can be accessed by an anonymous user. Otherwise, we expect that, at some point, someone will forget to add an [Authorize] attribute to their controller and introduce vulnerabilities into the webapp.
It is my understanding that what I'm trying to do could be achieved in previous versions of ASP .NET by adding the following statement in FilterConfig.cs:
filters.Add(new AuthorizeAttribute());
... except that FilterConfig.cs no longer exists in MVC 6. According to How to register a global filter with mvc 6, asp.net 5 I can now access the global filters list using:
services.ConfigureMvc(options =>
{
options.Filters.Add(new YouGlobalActionFilter());
}
... tried it, looks fine, but now it's the AuthorizeAttribute filter that I can't seem to find.
For experimenting purposes I've tried to handcraft an equivalent to the AuthorizeAttribute filter and came up with the following:
public class LoginFilter: AuthorizeFilter
{
public LoginFilter(): base(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())
{
}
public override Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.AuthorizationContext context)
{
if(!context.HttpContext.User.Identity.IsAuthenticated && context.ActionDescriptor is ControllerActionDescriptor)
{
var action = context.ActionDescriptor as ControllerActionDescriptor;
if(!AcceptAnonymous(action.ControllerTypeInfo) && !AcceptAnonymous(action.MethodInfo))
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
}
return base.OnAuthorizationAsync(context);
}
private static bool AcceptAnonymous(ICustomAttributeProvider o)
{
return o.IsDefined(typeof(AllowAnonymousAttribute), true);
}
}
This kinda works... I can add it to the global filters list, and it does reject queries coming from unauthenticated users unless the query is resolved to an action tagged [AllowsAnonymous].
However...
the AuthorizationPolicyBuilder thingy is ugly and misleading: it does not serve any purpose and is apparently ignored during the whole processing. The only reason I added it is that AuthorizeFilter requires an AuthorizationPolicy in its constructor. I guess, but haven't tried yet, that directly implementing IAsyncAuthorizationFilter would solve this particular issue
nothing in this code is specific to my webapp and the functionality was apparently provided in previous versions of the framework, so I'm willing to bet that there already is (or there will soon be) a component doing exactly the same thing, and I'd rather use a standard component from the framework than handcraft my own.
So, long story short, where has the AuthorizeAttribute filter gone? Or is there any functional equivalent I can use to make rejection of anonymous users the default behavior?
You can use Microsoft.AspNet.Mvc.AuthorizeFilter.
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Authorization;
services.ConfigureMvc(options =>
{
options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()));
});
If you need custom authorization requirements see this answer for more information.

Attribute based webapi2 routing returns 404 for some methods

I'm presently working on a project that has been upgraded to Webapi2 from Webapi. Part of the conversion includes the switch to using attribute based routing.
I've appropriately setup my routes in the Global.asax (as follows)
GlobalConfiguration.Configure(config => config.MapHttpAttributeRoutes());
and removed the previous routing configuration.
I have decorated all of my API controllers with the appropriate System.Web.Http.RouteAttribute and System.Web.Http.RoutePrefixAttribute attributes.
If I inspect System.Web.Http.GlobalConfiguration.Configuration.Routes with the debugger I can see that all my expected routes are registered in the collection. Likewise the appropriate routes are available within the included generated Webapi Help Page documentation as expected.
Even though all appears to be setup properly a good number of my REST calls result in a 404 not found response from the server.
I've found some notable similarities specific to GET methods (this is all I've tested so far)
If a method accepts 0 parameters it will fail
If a route overrides the prefix it will fail
If a method takes a string parameter it is likely to succeed
return type seems to have no affect
Naming a route seems to have no affect
Ordering a route seems to have no affect
Renaming the underlying method seems to have no affect
Worth noting is that my API controllers appear in a separate area, but given that some routes do work I don't expect this to be the issue at hand.
Example of non-functional method call
[RoutePrefix("api/postman")]
public class PostmanApiController : ApiController
{
...
[HttpGet]
[Route("all", Name = "GetPostmanCollection")]
[ResponseType(typeof (PostmanCollectionGet))]
public IHttpActionResult GetPostmanCollection()
{
return Ok(...);
}
...
}
I expect this to be available via http://[application-root]/api/postman/all
Interestingly enough a call to
Url.Link("GetPostmanCollection", null)
will return the above expected url
A very similar example of method calls within the same controller where some work and some do not.
[RoutePrefix("api/machine")]
public class MachineApiController : ApiController
{
...
[HttpGet]
[Route("byowner/{owner}", Name = "GetPostmanCollection")]
public IEnumerable<string> GetByOwner([FromUri] string owner)
{
...
}
...
[HttpGet]
[Route("~/api/oses/{osType}")]
public IEnumerable<OsAndVersionGet> GetOSes([FromUri] string osType)
{
...
}
...
}
Where a call to http://[application-root]/api/machineby/ownername succeeds and http://[application-root]/api/oses/osType does not.
I've been poking at this far too long, any idea as to what the issue may be?
Check that you configure your HttpConfiguration via the MapHttpAttributeRoutes method before any ASP.NET MVC routing registration.
In accordance to Microsoft's CodePlex entry on Attribute Routing in MVC and Web API the Design section states:
In most cases, MapHttpAttributeRoutes or MapMvcAttributeRoutes will be
called first so that attribute routes are registered before the global
routes (and therefore get a chance to supersede global routes).
Requests to attribute routed controllers would also be filtered to
only those that originated from an attribute route.
Therefore, within the Global.asax (or where registering routes) it is appropriate to call:
GlobalConfiguration.Configure(c => c.MapHttpAttributeRoutes()); // http routes
RouteTable.Routes.MapRoute(...); // mvc routes
In my case it was a stupid mistake, I am posting this so people behind me making the same mistake may read this before they check everything else at quantum level.
My mistake was, my controller's name did not end with the word Controller.
Happy new year

Custom AuthorizeAttribute not called

There are a lot of similar questions out there but this has me stumped.
If I used [Authorize] I get prompted for a username and password but if I use [InternalAuthorizeV2] I don't
I have a custom AuthorizeAttribute that for the moment does not do any anything special (I'm limiting things that could be wrong).
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class InternalAuthorizeV2Attribute: AuthorizeAttribute
{}
and a Action in my Controller
[InternalAuthorizeV2(Roles = "MobileApps_Parkingggg")]
public ActionResult Index()
{
var model = new VmParking();
return View(model);
}
The login is handled in a different app but they have identical web config lines
<machineKey compatibilityMode="Framework20SP2" validationKey="editedOut" decryptionKey="editedOut" validation="SHA1" decryption="AES"/>
<authentication mode="Forms">
<forms name="emLogin" loginUrl="/Login/Index" timeout="540" protection="All" path="/" enableCrossAppRedirects="true" cookieless="UseCookies" />
</authentication>
<sessionState timeout="540" />
I know that if I login by going to a page with [Authorize] then back to my trouble page I can see the username but it doesn't seem to be calling my customer attribute.
New information:
My attribute is in a shared DLL as it's used by many apps. It seems that if I copy the cs file to the web project it works. No idea why, still looking for hints or tips.
From what you've said, it all behaves fine if you use [Authorize] but not [InternalAuthorizeV2].
Your shared dll shouldn't make any difference if it is set up correctly; I have the same thing working. Make sure the web project is using the latest version of the dll and you have the right assembly references in the shared dll - System.Web.Mvc, v4.0.0.0 in my project.
You say its used by many apps? Do all apps have the same problem with the shared dll or just one of them? If it's just one, check the references for the one with the problem.
If the below tests all work then the final option is that whatever you are doing in your authorize attribute in the dll isn't picking up the right context for that app, or using the right membership provider or database - you haven't included the code you are using inside your attribute so it's hard to know if that could be causing a problem.
Test dependencies
You could try adding a base authorize attribute to your shared dll, and then implementing another authorize attribute in your web project that inherits the base attribute you just created. This should show that you have your shared dll set up correctly.
// in the dll:
public class BaseAuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute { ... }
// in the web project:
public class InternalAuthorizeV2Attribute : BaseAuthorizeAttribute { ... }
If simply moving it from your dll project to the web project fixes it, the most likely issue is the web project is not using the right version of the dll (try cleaning and doing a complete rebuild) or your dll is referencing the wrong dlls for the System.Web.Mvc.AuthorizeAttribute. You say you have triple checked, but trying the above debugging should help you work out if this really is the case.
Debug authorization method calls
If that doesn't work then try adding the following override methods to a very simple attribute, and seeing if you hit the breakpoint on the call to base.OnAuthorization. If you don't, then it may not be the actual attributes causing your problem.
[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Method,
Inherited = true, AllowMultiple = true)]
public class InternalAuthorizeV2Attribute : System.Web.Mvc.AuthorizeAttribute {
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext) {
return false; // breakpoint here, and this should force an authorization failure
}
public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext); // breakpoint here
}
}
This should completely prevent any user access to the Action. If that doesn't work then you know the issue doesn't lie in your attribute, but that your attribute is not being applied.
You can also add the following to your controller and check that it is hit before the authorize attribute:
protected override void OnAuthorization(AuthorizationContext filterContext) {
base.OnAuthorization(filterContext);
}
Authorization chaining
Note that you have your attribute attached to the Action method, so it will only be hit if a an authorization attribute earlier in the chain (e.g. a global filter or controller attribute) hasn't already prevented the user being authorized (see my answer here), or prematurely returns an ActionResult that stops the chain reaching your Action attribute. However it's unlikely this is the problem if simply moving it from the dll to the project makes it work. Similarly it's unlikely you have an AllowAnonymous in the wrong place from what you've said.

After creating separate Models and Controllers projects, I get "no suitable method found to override" on my Initialize method declaration

This is driving me insane... I am reorganizing my MVC app into a Models project and a Controllers project, and then the main application as a project. So, everything is working good so far except...
Whenever I go to "Rebuild" my controllers project, I get this error:
Controllers.AccountController.Initialize(System.Web.Routing.RequestContext)': no suitable method found to override.
Keep in mind that AccountController.cs was automatically placed in my application by Visual Studio, and this was all working fine when the Controllers were within my main project. I think it might have to do with the ASPNETDB.MDF file that this AccountController.cs file references to authenticate users as they log in, since this database stayed within my main project and didn't follow the Controllers project. Thoughts on that??
Here's the Initialize method on my AccountController:
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
PLEASE HELP!!! Thanks in advance!!
this error message suggests that your AccountController class isn't being derived from MVC's Controller base class.
It turns out what I was missing was a reference to System.Web.Routing. I had using System.Web.Routing, and it wasn't marked "red" (meaning, it couldn't find it), so I just assumed it was there. So this fixed the problem.

Redirect away from HTTPS with ASP.NET MVC App

I'm using ASP.NET MVC 2 and have a login page that is secured via HTTPS. To ensure that the user always accesses those pages via SSL, I've added the attribute [RequireHttps] to the controller. This does the job perfectly.
When they have successfully logged in, I'd like to redirect them back to HTTP version. However, there isn't a [RequireHttp] attribute and I'm struggling to get my head around how I might achieve this.
The added (potential) complication is that the website when in production is hosted at the route of the domain, but for development and testing purposes it is within a sub directory / virtual directory / application.
Am I over-thinking this and is there an easy solution staring me in the face? Or is it a little more complex?
After a bit of digging, I went along the lines of rolling my own as there didn't appear to be a good built-in solution to this (as mentioned, there is a great one for MVC2 applications in the form of [RequireHttps]). Inspired by çağdaş's solution to this problem and I adapated to come up with the following code:
public class RequireHttp : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// If the request has arrived via HTTPS...
if (filterContext.HttpContext.Request.IsSecureConnection)
{
filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.Url.ToString().Replace("https:", "http:")); // Go on, bugger off "s"!
filterContext.Result.ExecuteResult(filterContext);
}
base.OnActionExecuting(filterContext);
}
}
I can now add this to my Controller methods and it behaves (seemingly) as expected. If I redirect to the Index action on my controller from a HTTPS protocol, it will redirect to HTTP. It only allows HTTP access to the Index ActionResult.
[RequireHttp]
public ActionResult Index() {
return View();
}

Categories

Resources