Enforce https on one controller and http on the other - asp.net-core

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.

Related

Block server-to-server or Postman calls to ASP.NET Core 3.1 API

I have a ASP.NET Core 3.1 API where I have not used CORS. As I understand, CORS is a browser thing. And as my ajax calls from another site on another origin is blocked to the API endpoints (which is great), I can still reach the same endpoints by using Postman or a HttpClient and GetAsync() calls.
My question is of it's possible to also block server-to-server calls (or Postman calls) to my API? Or like CORS, only allow certain origins?
Most of my endpoints are protected by a bearer JWT token, but I have an anonymous endpoint that I would like to let only origins I control (or can configure) to have access to that anonymous API.
I solved it after i bumped in to this post on stackoverflow:
How do you create a custom AuthorizeAttribute in ASP.NET Core?
I simply made a custom Authorize attribute [ApiAuthorize()], that I call this way:
[ApiController]
[ApiAuthorize(new string[] { "https://localhost:44351", "https://mysite.onthe.net" })]
public class MyInternalApiController : ControllerBase
{
...
}
It may also be implemented on the Action instead of the Controller. The implementation was done like this:
public class ApiAuthorizeAttribute : TypeFilterAttribute
{
public ApiAuthorizeAttribute(string[] origins) : base(typeof(ApiAuthorizeFilter))
{
Arguments = new object[] { origins };
}
}
public class ApiAuthorizeFilter : IAuthorizationFilter
{
readonly string[] _origins;
public ApiAuthorizeFilter(string[] origins)
{
_origins = origins;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (_origins == null)
return;
string referer = context.HttpContext.Request.Headers["Referer"].ToString();
if (string.IsNullOrWhiteSpace(referer) || !_origins.Any(origin => referer.StartsWith(origin, StringComparison.OrdinalIgnoreCase)))
context.Result = new ForbidResult();
}
}
Things to consider:
The implementation and check of the referer could be exact match instead of StartsWith
The handling could use RegEx or any good alternative to handle subdomains, wildcards etc
The referer could be translated to a Uri objects to get better results and variations
A jQuery ajax call gets a "403 - Forbidden" as expected, but Postman gets a "404 - Not Found". To me that does not matter, but that's something to look into if it matters.
But it covers what I need, so I'm happy with this.

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

Routing is not working for web api with realtions

I am creating web api using asp.net core. The api end point is logically mapped to resource's relations based on guidelines here
So my API looks like
http://tax.mydomain.com/api/v1/clients/1/batches/12/start
Where Client is parent of Batch, 1 is clientid and 12 is batchid, and Start is POST action method.
Here is the corresponding controller
public class TaxController : Controller
{
[HttpPost]
[Route("clients/{clientid}/batches/{batchid}/start")]
public void Start([FromRoute]string clientId, [FromRoute]string batchId,
[FromBody]IEnumerable<string> urls)
{
// do something
}
}
since api/v1 is common to all controllers i configured that in startup's Configure method. Also i want Home as default controller.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMvc(routes =>
{
routes.MapRoute("default","api/v1/{controller=Home}/{action=Index}/{id?}");
});
}
However client is getting not found error for api http://tax.mydomain.com/api/v1/clients/1/batches/12/start
Any controller methods that do not have a route attribute use convention-based routing.
When you use [Route] attribute, you define attribute routing and so conventional routing is not used for that action/controller. Therefore, your controller is accessible by
http://tax.mydomain.com/clients/1/batches/12/start
As an option, you can use the fact, that attribute routes can be combined with inheritance. Set a Route attribute on the entire controller and this will work as route prefix (the same behavior as [RoutePrefix] attribute in WebApi):
[Route("api/v1")]
public class TaxController : Controller
{
}
More general example from routing documentation:
[Route("api/[controller]")]
public abstract class MyBaseController : Controller { ... }
public class ProductsController : MyBaseController
{
[HttpGet] // Matches '/api/Products'
public IActionResult List() { ... }
[HttpPost("{id}")] // Matches '/api/Products/{id}'
public IActionResult Edit(int id) { ... }
}
There are two things wrong with your setup
You call http://tax.mydomain.com/clients/1/batches/12/start but you don't have specified the controller name within it. This route looks for a controller named ClientsController. So the correct url would have to be http://tax.mydomain.com/tax/clients/1/batches/12/start instead
You seem to be using default MVC/Viewbased route, but your url suggest you use WebAPI.
When you use WebAPI to create a Rest service, you don't have any actions. Instead, actions map to the Http Verbs (GET (Read), PUT (update/replace), POST (insert), DELETE).
So for REST Services your default route should look like this instead: api/v1/{controller=Home}/{id?}

Apply a browser caching policy to all ASP.NET Core MVC pages

I'd like to tell ASP.NET Core to add a common Cache-Control and related headers to all responses served by MVC controllers, both HTML pages and Web API responses. I don't want the policy to apply to cache static files; those I want to be cached.
In particular case, I want to disable caching, the equivalent of applying this attribute to all controllers:
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
I could made a base controller with this attribute and derive all other controllers from it. I was wondering if there is a configuration-based approach that avoids the need for such a base controller.
You can do it w/o middleware just by adding
services.AddMvc(options => {
options.Filters.Add(new ResponseCacheAttribute() { NoStore = true, Location = ResponseCacheLocation.None });
})
in your ConfigureServices method. Works with any Attribute (i.e AuthorizeAttribute), which can be instantiated and will be applied to all controllers and actions. Also no need for a base class.
As #DOMZE said, you may consider using a custom middleware. Actually, response caching is already implemented as a middleware.
But as you want to add caching only to all MVC actions, the better way is to use MVC action filters. One of the benefits is that you may apply filter only to specific controller/action + you will have access to ActionExecutingContext (controller instance/ action arguments )
public class ResponseCacheActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// before the action executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
// after the action executes
// add here a common Cache-Control and related headers to all responses
}
}

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.