Redirect away from HTTPS with ASP.NET MVC App - c#

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();
}

Related

RequireHttps vs deriving from RequireHttpsAttribute

I'm trying to implement HTTPS on selected pages of my site. Using the attribute RequireHttps works but causes problems testing as we don't have a cert installed locally.
The solution I'm looking for will need to ignore localhost and ignore one test server while working on our second test server where we do have a cert in place.
Some further background on this. The aim is to move the site gradually to https. It's an ecommerce site so obviously portions are already secure and I know that for many reasons moving the entire site to secure is a good thing. I also know that once you move from Page A to Page B where B is secure then it won't go back to HTTP when you move back to A, that's fine.
I want to move the site in stages just in case there are problems with things like mismatched content, site maps, SEO, google ranking etc.
Some of the various solutions I have tried - I've implemented a class derived from the RequireHttps attribute as follows:
public class CustomRequireHttps : RequireHttpsAttribute
{
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.Url != null && (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)
&& !String.Equals(filterContext.HttpContext.Request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase)
&& !filterContext.HttpContext.Request.Url.Host.Contains("localhost")
&& !filterContext.HttpContext.Request.Url.Host.Contains("testing")))
{
base.HandleNonHttpsRequest(filterContext);
}
}
}
And have applied this attribute to one page but it hasn't worked as intended, it either applies HTTPS to all pages on the site or doesn't work at all.
I have also tried this solution which works but only on localhost and not on the two test servers:
#if !DEBUG
[RequireHttps]
#endif
Then I tried overriding the OnAuthorizartion method like so:
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.HttpContext != null && filterContext.HttpContext.Request.IsLocal)
{
return;
}
base.OnAuthorization(filterContext);
}
It worked locally but once I got it onto the server with the test cert suddenly every page is HTTPS which I do not understand as I've only used this derived attribute on one page.
So, what I'm looking to achieve is to implement HTTPS on a select number of pages on my site. This HTTPS request needs to be ignored on localhost and the first test server but, it needs to NOT to be ignored on the second test server which has a cert.
So far it either doesn't work at all or is on every page on the site.
However, and this is the kicker, If I use the RequireHttps attribute it works perfectly on the second test server but causes problems on all servers without a cert. By 'works perfectly' I mean it implements HTTPS only on the pages where I've used that attribute and does not suddenly switch all pages to secure.
Any ideas what I'm doing wrong here?
There can be a lot going on, for example when your links are local, when a switch is made to HTTPS, all pages are HTTPS (not applying require HTTPS doesn't switch back to HTTP). From a security standpoint, you should serve all pages from HTTPS when you need it for a subset of pages (otherwise, you might share secure cookies / login tokens over unencrypted HTTP). So probably your attribute is applied, and all subsequent requests are served over SSL.
Secondly, testing on localhost request uri will serve the page over HTTP on your second server. My opinion to solve this problem is to create a switch in your web.config if the pages should be served over HTTPS. Check this switch in your global filterConfig:
public static class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
var useSsl = Convert.ToBoolean(ConfigurationManager.AppSettings["useSsl"]);
if (useSsl )
{
filters.Add(new RequireHttpsAttribute());
}
}
}

How can I use asp.net MVC Areas to setup an application to serve my different clients using same code base

I am seeking help in setup an application so that I can give my clients their own url for browsing.
I am thinking about creating asp.net MVC application and by using AREAS feature ( I will consider each area as my client) I will develop individual application for my client and provide them the url which will serve a their own application running.
Basically, I want to keep my all clients in one application but give them different url.
As areas works as follows:
localhost:5699 -- it will land to default home controller and index page
ocalhost:5699/area1/home/index - it lands to Home controller or Area and renders index view of this area
and so on for another area.
So. I want to ask, can i use this approach to give my clients unique url which I can map to particular Area of application and client can browse simple typing their url and that land to index page of that area?
for example:
www.area1.com -- I want to map this url to localhost/5699/area1/home/index.aspx
www.area2.com -- I want to map this url to localhost/5699/area2/home/index.aspx
Please help, how can i will setup all above in production and development environment
Basically, i want to setup my application such that if my client want different UI and additional functionality I can easily alter respective controller.
If I understand the question correctly, I think you can accomplish your goal by way of the Route and RoutePrefix attributes. These attributes will decorate controllers and methods and give you the ability to tweak the URL's exactly how you want it.
localhost/5699/area1/home/index
[RoutePrefix("area1")]
public class Area1Controller: ApiController
{
[Route("home/index")]
public ActionResult Index()
{
// controller logic here
}
}
localhost/5699/area2/home/index
[RoutePrefix("area2")]
public class Area2Controller: ApiController
{
[Route("home/index")]
public ActionResult Index()
{
// controller logic here
}
}

How to use ServiceStack authentication correctly in ASP.Net MVC controller

I'm having problem with getting ServiceStack [Authentication] attribute to work in ASP.Net MVC4 controller, pages / action methods with the attribute keep redirecting Users to the login page even after the login details are submitted correctly.
I've followed the SocialBootstrapApi example, with the difference being that all the authentication web service calls are made from the controllers:
this.CreateRestClient().Post<RegistrationResponse>("/register", model);
Other things that I've done so far:
Use my own user session implementation subclassing AuthUserSession (not too different from the example, but using my own implementation of User table)
Inherit ServiceStackController on my BaseController, overriding the default login URL
Enable Auth feature in AppHost with my user session implementation
Registration does work, user auth logic works (even though the session does not persist), and I can see the ss-id and ss-pid cookies in the request.
So my complete list of questions:
How do I make the [Authenticate] attribute work (or, what did I do wrong)?
How do I save and reuse the user session in an MVC controller? At the moment this.UserSession is always null.
How do I logout a user? this.CreateRestClient().Get<AuthResponse>("/auth/logout"); does not seem to work.
Update 1:
The session cookies (ss-id and ss-pid) gets created when I attempt to load the secured page (ones with [Authenticate] attribute), before any credentials get submitted. Is this the expected behaviour?
Update 2:
I can see that the session is saved in MemoryCacheClient, however trying to retrieve it in the base controller via this.Cache.Get<CustomUserSession>(SessionKey) returns null (where SessionKey is like: urn:iauthsession:1)
After much fiddling around, apparently the way to hook ServiceStack authentication is to call the AuthService via:
try {
authResponse = AuthService.Authenticate(new Auth{ UserName = model.UserName, Continue = returnUrl, Password = model.Password });
} catch (Exception ex) {
// Cut for brevity...
}
and NOT authResponse = this.CreateRestClient().Post<AuthResponse>("/auth/credentials", model);!
Where AuthService is defined in the base controller as:
public AuthService AuthService
{
get
{
var authService = ServiceStack.WebHost.Endpoints.AppHostBase.Instance.Container.Resolve<AuthService>();
authService.RequestContext = new HttpRequestContext(
System.Web.HttpContext.Current.Request.ToRequest(),
System.Web.HttpContext.Current.Response.ToResponse(),
null);
return authService;
}
}
Everything else (incl. session) works correctly now.
You can find how it could be done in the ServiceStack Use Cases repository. The following example is based on MVC4 but works perfectly for MVC3 either: CustomAuthenticationMvc.

How to prevent unauthorized child action to return 401

Problem
Long story short, how can you prevent unauthorized ChildActions to return a 401 code and return an empty result instead.
Context
My application uses NTLM to authenticate on the network. I also want to handle anonymous identification, for non NTLM capable devices or if someone goes through some proxy.
One the the web application, there is controller/actions that absolutely require to be logged in to be viewable and some other that are viewable as anonymous. For the ones that authentication is mandatory where I initially used the following:
[Authorize()]
public class SomeController : BaseController
Which works as expected and returns a 401 Status Code (Unauthorized).
The problem starts on the page that allows anonymous. Because those pages have parts that uses RenderAction to render pieces that requires authentication.
On local developpement server on front end everything looks fine, but with IIS any page that have any small piece that requires authentication it returns 401 page. So I created a custom Authorize attribute:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public bool Ignore401ChildAction { get; set; }
public CustomAuthorizeAttribute()
: base() {
this.Ignore401ChildAction = true;
}
protected override void HandleUnauthorizedRequest( AuthorizationContext filterContext ) {
base.HandleUnauthorizedRequest( filterContext );
if( this.Ignore401ChildAction && filterContext.IsChildAction ) {
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
}
And then on frontend it seems to produce correct behavior with and without IIS. However in the backend, even if the page renders completly it returns a 401
So I added the following to my CustomAttribute instead of TrySkipIisCustomErrors
filterContext.HttpContext.Response.StatusCode = 200;
filterContext.Result = new EmptyResult();
In anonymous everything is fine, but when NTLM is enabled server doesn't request authentication when the controller doesnt have that CustomAuthorize attribute. It seems like its only requesting the crendetials when their mandatory instead of when they're available.
Thank you,
EDIT
After lot of searching, digging and fiddling, realized that true problem is that Anonymous identification will take precedence over Windows/NTLM. If Anonymous and Windows authentication are enabled, Windows will only execute if anonymous authentication fails.
SOLUTION(not perfect and kind of hackish but it works)
Add the following function to your BaseController then call from the views that do not mandatory require authentication, or simply call it from your _Layout view.
NOTE: Even though page will render correctly as anonymous, a 401 status code will still be returned.
[CustomAuthorize]
public ActionResult Auth() {
return new EmptyResult();
}

URL Routing across multiple subdomains

I find myself in a difficult situation. We're working on an ASP.NET MVC 2 application which is comprised of multiple sections. It is a design goal to have these sections span across several subdomains. Each subdomain will have its own controller.
The challenge is that our hosting provider's control panel allows two forms of redirection for subdomains, and neither of them seem to fit the bill. The choices are:
Redirecting to URL. Choice is given whether to redirect to an exact destination or a destination relative to the request URL.
Redirecting to a specific folder in my hosting space.
I'll try to illustrate the intended behaviour. Assuming the default route is {controller}/{action}/{id}, I'd like the URL http://subdomain.welcome.com/a/b be handled by the MVC Application like http://welcome.com/subdomain/a/b.
The URL redirection could solve this problem, except for the fact that the user sees a URL change occur in the browser. We don't want the client to see the redirection occur.
Redirecting to our MVC apps root folder doesn't work at all. The app doesn't pick up the request and a 4xx error gets passed back by IIS.
edit:
In the interest of finding an answer, I'll simplify this a bit. The "redirect to URL" doesn't do what I want so that leaves redirecting to a folder.
If I'm redirecting a subdomain to the root folder of my MVC App and IIS wont pick up the requests, is this a limitation of IIS or my provider?
Can you make your hosting website host headers respond to *.mydomain.com? Meaning, can your website take request for any sub domain of your primary domain? If so, then reference this post on how to handle subdomain routing in MVC apps and you should be good to go.
I would update the code in the post to this however, to make the code more succinct. In any case, make sure you have your 404 errors in place for people attempting to go to subdomains that don't exist.
public class ExampleRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var url = httpContext.Request.Headers["HOST"];
var index = url.IndexOf(".");
if (index < 0)
return null;
var subDomain = url.Substring(0, index);
var routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values.Add("controller", subdomain); //attempts to go to controller action of the subdomain
routeData.Values.Add("action", "Index"); //Goes to the Index action on the User2Controller
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//Implement your formating Url formating here
return null;
}
}
Not sure if this is overkill (this is actually used to serve pages from a zip file or resource file, etc), BUT... perhaps you could use a Virtual Path Provider?..
Implement a class that inherits from VirtualPathProvider, and register it in global startup like so:
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
Then implement a class that inherits from VirtualFile and serve it from the GetFile() override in your virtual path provider implementation:
public override VirtualFile GetFile( string virtualPath )
{
if( IsVirtualFile(virtualPath) )
return new MyVirtualFile(virtualPath);
return base.GetFile(virtualPath);
}
Note: IsVirtualFile is a function you would have to implement, based on the rules you have regarding the URL format, etc.

Categories

Resources