How to remove subdomains except one page in MVC - asp.net-mvc-4

Users navigate in web site with subdomains like
xxx.abc.com/userpage/133,
yyy.abc.com/userpage/133,
zzz.abc.com/userpage/133,
qqq.abc.com/userpage/133
As you can see there are several subdomains exits in our website.
I want to remove subdomains if user leaves from /userpage/
Such as, if navigates to :
/ContactUs,
/Register
/ (root)
How can I remove subdomains except just one action in MVC 4.
Thanks.

The best method would be to let IIS handle it. You can use the URL Rewrite module to redirect any request other than /userpage/ where the domain is not the main one you want to use to the main domain.
If you insist on doing it through the MVC pipeline, your best bet is to use an action filter:
public class RedirectSubdomainFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.RequestContext.HttpContext.Request;
if (request.Url.Host != "maindomain.com" && !request.Url.AbsolutePath.StartsWith("/userpage/"))
{
filterContext.Result = new RedirectResult("http://maindomain.com" + request.Url.PathAndQuery);
}
base.OnActionExecuting(filterContext);
}
}
Then, in App_Start\FilterConfig.cs add:
filters.Add(new RedirectSubdomainFilter());
EDIT (05/26/2016)
I changed the action filter code above to use StartsWith instead of a direct comparison, as I neglected to notice that the URL may contain something after /userpage/.

Related

Override routing in ASP.NET CORE 2.2 to implicitly route to an area if user have some permissions

I'm looking for an easy way to change routing behaviour a little and add extra area data into route data if the user has some sorts of permissions.
Let's say for regular user url site/shop/12 should route to ShopController
but for admin it should route to AdminArea/ShopController
Please, consider that this question isn't about HTTP redirect, it's about extending infrastructure on a framework level to allow extra functionality on Routing or controller invocation
You could use URL Rewriting Middleware to redirect the request for Admin user
1.Create a Redirect rule:
public class RewriteRules
{
public static void RedirectRequests(RewriteContext context)
{
//Your logic
var IsAdminRole = context.HttpContext.User.IsInRole("Admin");
if (IsAdminRole)
{
var request = context.HttpContext.Request;
string area = "AdminArea";
var path = request.Path.Value;
//Add your conditions of redirecting
if(path.Split("/")[1] != area)// If the url does not start with "/AdminArea"
{
context.HttpContext.Response.Redirect($"/{area}{ request.Path.Value }");
}
}
}
}
2.Use the middleware in Startup Configure method:
app.UseAuthentication();//before the Rewriter middleware
app.UseRewriter(new RewriteOptions()
.Add(RewriteRules.RedirectRequests)
);
Add logic to the controller method that handles site/shop/12 to check if the user is an admin, and if it is, redirect to to the proper admin area and controller.
var isAdmin = IsUserAnAdmin();
if (isAdmin) {
// This will redirect to the Index method defined in the ShopController
// in the area name AdminArea
return RedirectToAction("Index", "Shop", new { Area = "AdminArea" });
}
I think the best way is to set the correct URLs on the front-end and then validate the request on the end-point doing something like this:
[HttpGet]
[Route("v1.0/download/document")]
public IActionResult download_document(int id, string token)
{
try
{
if (token == null || isNotAdmin(token))
return Unauthorized();
That way your end-points are protected and you avoid redirections. Plus, in my opinion everything makes a lot more sense on the front-end

Enforce https on one controller and http on the other

I am using ASP.NET core 2.1,
I am looking for a way to enforce https on one controller and http on the other.
The following document shows how to enforce HTTPS for the whole ASP.NET core, but not for individual controller.
https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-2.1&tabs=visual-studio
One approach would be to make use of two action filters: one for enforcing HTTPS redirects, and another for allowing HTTP requests. The first one would be registered globally, and the second used just with controllers/actions you wish to allow HTTP traffic to. As an example:
[AllowHttp]
public class HomeController : Controller
Where AllowHttp is defined as:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
// Inheriting from ActionFilterAttribute allows this to show
// up in the ActionExecutingContext.Filters collection.
// See the global filter's implementation.
public class AllowHttpAttribute : ActionFilterAttribute
{
}
Next, the global filter:
// Needed for the GetEncodedUrl() extension method.
using Microsoft.AspNetCore.Http.Extensions;
public class RedirectToHttpsActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Filters.Any(x => x is AllowHttpAttribute))
{
return;
}
if (!context.HttpContext.Request.IsHttps)
{
var insecureUri = context.HttpContext.Request.GetEncodedUrl();
var secureUri = insecureUri.Replace("http://", "https://");
// As you're likely trying this out locally, you'll need to specify
// the port to redirect to as well. You won't need this on production.
// Change the first port to your HTTP port and the second to your HTTPS port.
secureUri = secureUri.Replace(":49834", ":44329");
context.Result = new RedirectResult(secureUri);
}
}
}
Finally, you'll have to register the filter globally in your Startup.cs:
services.AddMvc(options =>
{
options.Filters.Add(new RedirectToHttpsActionFilter());
});
I'm sure you could achieve the same thing with URL rewriting, but in the case where you may change your controller routes, this will carry on working without intervention.

HttpContext.Current.Request is not available in RegisterGlobalFilters

I am trying to add RequireHttpsAttribute attribute to MVC filters collection to push web site to HTTPS when it is deployed on prod server. The problem is with HttpContext.Current.Request.IsLocal line, the Request object is not available yet. Then how to check is site running localy or on prod server in RegisterGlobalFilters?
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
if (!HttpContext.Current.Request.IsLocal) //Exception here!!!
{
filters.Add(new RequireHttpsAttribute());
}
}
In this method you are to register the filters that will do the checking when the request comes in. This method will only get called once each time the application is started. So here you need to do something along the lines of:
filters.Add(new MyAuthorizeAttribute());
With MyAuthorizeAttribute being something along the lines of:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
If(!httpContext.Request.IsLocal)
{
**//Check for HTTPS and return false if need be**
}
}
Of course it does not need to be an AuthorizeAttribute.
EDIT
As I said before this method is called only once at the start of the application so there is no request for you to check in here. Here you can only apply filters that will be called every time a request is received. It is inside those filters that you can check request specific properties.
If you insist on using the RequireHttpsAttribute, than you either have to apply it to all methods regardless of whether the request is local or not or you have to extend RequireHttpsAttribute and override HandleNonHttpsRequest to handle local requests.

Redirect HTTP to HTTPS in MVC4 Mobile Application

In My MVC4 Mobile application i have registration, login page and remaining pages. i would like to redirect user to HTTPS connection for all sensitive information pages like registration and login pages and HTTP to remailing pages.
I prefer you to use conditional functionality putting the class
public class RequireHttpsConditional : RequireHttpsAttribute
{
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
var useSslConfig = ConfigurationManager.AppSettings["UseSSL"];
if (useSslConfig != null)
{
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The requested resource can only be accessed via SSL.");
}
var request = filterContext.HttpContext.Request;
string url = null;
int sslPort;
if (Int32.TryParse(useSslConfig, out sslPort) && sslPort > 0)
{
url = "https://" + request.Url.Host + request.RawUrl;
if (sslPort != 443)
{
var builder = new UriBuilder(url) { Port = sslPort };
url = builder.Uri.ToString();
}
}
if (sslPort != request.Url.Port)
{
filterContext.Result = new RedirectResult(url);
}
}
}
}
and using this [RequireHttpsConditional] above the action result.
i have got this code somewhere in internet and is working fine for me.
in web.config appsettings use <add key="UseSSL" value="443" />
and in the controller above the action result you need put
[RequireHttpsConditional]
public ActionResult SignIn()
{
}
In IIS where you have your project right click and click "Edit Bindings" then you add a custom type https and port no 443 (you can change it)
Note this will work only in production environment. when executed locally it wont be working.
When you execute it locally you have request.Url.Host which will return you only localhost and missing your port number. so if you use it in MVC you will find error loading page for your pages where you put this code.
So this will work when you have the host assigned instead of using the localhost with a specific port number.
Within the controller actions that you wish to be HTTPS add the following code to the top of the method (of course you can simply add this to its own method and then call it):
if (!HttpContext.Request.IsSecureConnection)
{
var url = new UriBuilder(HttpContext.Request.Url);
url.Scheme = "https";
Response.Redirect(url.Uri.AbsoluteUri);
}
It is recommended though that you keep HTTPS on throughout your site to protect against a MITM attack against the auth cookie.

URL rewrite in ASP.NET 4.5 and Web API

We've got a long-running ASP.NET web-forms application that was born in the .NET 1.1/IIS6 days. We're now on .NET4.5/IIS7 but we've done nothing with MVC.
We provide a catalog to customers and give them a URL they can use:
www.ourhost.com/customername
Using a custom IHttpModule we developed we pull 'customername' out of the URL to find the customer in the database. That customer's ID is then stored in the page's context* and used by virtually all the pages on the site to customize content for that customer. After this process, the above URL would be rewritten and processed as
www.ourhost.com/index.aspx
with index.aspx having access to the customer's ID via its context and it can do its thing.
This works great and we support several thousand customers with it. the rewriting logic is fairly complex because it validates customer accounts, redirects to a 'uh oh' page if the customer is invalid and to a different 'find a dealer' page if the customer has not paid, etc. etc.
Now I'd like to build some Web API controllers and MVC-style rewriting has me worried. I see many examples where rewriting happens to make URL's like this work:
www.ourhost.com/api/{controller}
but I still need these web api 'calls' to happen in the context of a customer. Our pages are getting more sophisticated with JSON/AJAX async calls but in answering those calls I still need customer context. I would like the URL's to be
www.ourhost.com/customername/api/{controller}
But I am stumped as to how to configure routing to do this and have it play nicely with our IHttpModule.
Is this even possible?
*UPDATE: When I say 'stored in the page context' I mean the HttpContext associated with each web request that includes a dictionary where I can store some page/request-specific data.
There are two parts of the answer to your issue that I can see.
Maintaining the User Info across multiple requests
Generally an MVC API application will be stateless, that is you do not retain the current users session state between requests. Well that is what I have learned or been preached many times when writing RESTFul APIs.
That been said, you can enable session state in MVC Web API by adding the following to your global.asax.cs
protected void Application_PostAuthorizeRequest()
{
// To enable session state in the WebAPI.
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
Authorising A Customer in the Request
As you have shown in the Request URL you could add the customer name, then capture that and pass it to the same routine that your current http module calls to authorise on request. You could do this with an MVC Filter.
First do a similar URL Pattern to capture your customers name in the WebApiConfig.cs, something like so;
config.Routes.MapHttpRoute(
name: "WithCustomerApi",
routeTemplate: "api/{customername}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then add an ActionFilter to your API Controller which processes each request, checks current session info and if needed calls your authorise/customer lookup code and then saves to session state for later use. Or if no good info from customer can send to a new MVC route
So you will add an attribute something like so;
[WebApiAuthentication]
public class BaseApiController : ApiController
{
}
Then create an action filter that might look like this (note I have not tested this, just done for a pattern of how to).
public class WebApiAuthenticationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var routeData = actionContext.ControllerContext.Request.GetRouteData();
var currentContext = HttpContext.Current;
if (routeData.Route.RouteTemplate.Contains("customername"))
{
try
{
var authenticated = currentContext.Request.IsAuthenticated;
if (!authenticated)
{
var customer = routeData.Values["customername"];
// do something with customer here and then put into session or cache
currentContext.Session.Add("CustomerName", customer);
}
}
catch (Exception exception)
{
var error = exception.Message;
// We dont like the request
actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
else
{
// No customer name specified, send bad request, not found, what have you ... you *could* potentially redirect but we are in API so it probably a service request rather than a user
actionContext.Response = new HttpResponseMessage(HttpStatusCode.NotFound);
}
}
}
If you create a new MVC 5 Web API Application and add in these extras and put the filter on the default values controller like so you should be able to see this running as demo of a possible solution.
This will echo the customer name back if all works ok.
[WebApiAuthentication]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
var session = HttpContext.Current.Session;
if (session != null)
{
return new string[] {"session is present", "customer is", session["CustomerName"].ToString()};
}
return new string[] { "value1", "value2" };
}
}
I offer this up as a possible solution as I say, there are religious arguments about storing session and authorising in an API but those are not the question.
Hope that helps,
Steve