I've read thru many of the questions on ASP.NET MVC [RequireHttps] - but can't find the answer to this question:
How do you make the [RequireHttps] attribute switch the url to https if it was not https to start with?
I have this code:
public ActionResult DoSomething()
{
return View("AnotherAction");
}
[RequireHttps]
public ActionResult AnotherAction()
{
return View();
}
But I get an error saying: "The requested resource can only be accessed via SSL."
The MVC futures project has a similar attribute [RequireSsl(Redirect = true)]. But that is outdated now ... What is the equivalent in MVC 2?
When someone types in the URL http://example.com/home/dosomething OR the url http://example.com/home/anotheraction, I need them to be automatically redirected to the url https://example.com/home/anotheraction
EDIT this is the sequence of events:
The URL http://example.com/home/dosomething is called from another website. They redirect their users to this url (with a response.redirect or similar).
DoSomething() then tries to return AnotherAction(), but fails with the error message "The requested resource can only be accessed via SSL."
The RequiresHttps attribute does automatically attempt to redirect to https://your-url. I verified this behavior on a site I have that uses that attribute, and also looking at the code in Reflector:
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(MvcResources.RequireHttpsAttribute_MustUseSsl);
}
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
Are you sure you have your site set up to accept secure connections? What happens if you try to browse to https://your-url directly?
[mvc 4] short answer:
protected void Application_BeginRequest(Object source, EventArgs e)
{
if (!Context.Request.IsSecureConnection)
{
Response.Redirect(Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
longer answer:
to move from http to https you cant send a redirect to https after the first packet,
therefor you need to catch the packet using Application_BeginRequest,
from the Global.asax add the function and it will override the default,
the code should be something like so (Global.asax on the class level):
protected void Application_BeginRequest(Object source, EventArgs e)
{
if (!Context.Request.IsSecureConnection &&
!Request.Url.Host.Contains("localhost") &&
Request.Url.AbsolutePath.Contains("SGAccount/Login"))
{
Response.Redirect(Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
i strongly suggest putting a breakpoints and inspecting the Request.Url object for any url related need.
or visit the msdn page confused about request.url absoluteuri vs originalstring?
so am i you can go to dotnetperls for examples.
this function enables you to develop on localhost and deploying your code as is.
now for every page you want to make a https redirect you need to specify it in the if condition.
to move from https to http you can use regular Response.Redirect like so:
if (Request.Url.Scheme.Contains("https"))
{
Response.Redirect(string.Format("http://{0}", Request.Url.Authority), true);
}
notice this also support working on the same code when developing on local host by not interrupting the original course of things pre the https addition.
also i recommend thinking about implementing some return url convention (if not already implemented) in that case you should go something like so:
if (Request.Url.Scheme.Contains("https"))
{
Response.Redirect(string.Format("http://{0}{1}", Request.Url.Authority, returnUrl), true);
}
this will redirect to the requested page post login.
naturally you should protect every page that shows user data, register, login and more.
Http HEAD requests do not appear to be redirected. When reviewing our error logs we see lots of this message, googling lands here, but after looking more at the details they have a few interesting "features"
Request_method: HEAD
User Agent: curl/7.35.0
In other words all of the failed attempts were not customer facing...
(100% credit to comment from #arserbin3 for making me realize they were all HEAD requests)
MVC4 does now redirect
but not how you would expect.
http://www.example.com:8080/alpha/bravo/charlie?q=quux
will be redirect the client's browser to
https://www.example.com/alpha/bravo/charlie?q=quux
Notice the lack of a port number.
http://aspnetwebstack.codeplex.com/SourceControl/latest#test/System.Web.Mvc.Test/Test/RequireHttpsAttributeTest.cs
code test
[Fact]
public void OnAuthorizationRedirectsIfRequestIsNotSecureAndMethodIsGet()
confirms this is the desired behaviour.
If you would like to write a custom attribute that does include the PORT ... you can base your code on:
http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/RequireHttpsAttribute.cs
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
public RequireHttpsAttribute()
: this(permanent: false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequireHttpsAttribute"/> class.
/// </summary>
/// <param name="permanent">Whether the redirect to HTTPS should be a permanent redirect.</param>
public RequireHttpsAttribute(bool permanent)
{
this.Permanent = permanent;
}
/// <summary>
/// Gets a value indicating whether the redirect to HTTPS should be a permanent redirect.
/// </summary>
public bool Permanent { get; private set; }
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.Request.IsSecureConnection)
{
HandleNonHttpsRequest(filterContext);
}
}
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET requests, otherwise the browser might not propagate the verb and request
// body correctly.
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(MvcResources.RequireHttpsAttribute_MustUseSsl);
}
// redirect to HTTPS version of page
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url, this.Permanent);
}
}
To supplement the answer already given, this is the code from the MVC 5 implementation of HandleNonHttpsRequest
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET requests, otherwise the browser might not propagate the verb and request
// body correctly.
...
}
Related
I am working on My SAAS based application where I am facing one problem related to
requirement , my Application should be open from authenticated system only ,and it should be based on IP address. I will give permission from my database which IP address is authenticated. and It will work accordingly .. I have not tried any code because I have no Idea about this.
You can do this with an attribute that implements the IAuthorizationFilter interface. This will get called during the authorization checks done on each request.
For instance:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class IPFilterAttribute : Attribute, IAuthorizationFilter
{
/// <summary>Invoked during authization checks for page load</summary>
/// <param name="filterContext">Context of call, contains request and so on</param>
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
var request = filterContext?.HttpContext?.Request;
if (request == null)
throw new ArgumentNullException(nameof(filterContext));
if (!CheckIPAddress(request.UserHostAddress))
// Setting the Result property on filterContext stops processing.
filterContext.Result = new HttpUnauthorizedResult("Address Forbidden");
}
/// <summary>Check if the supplied IP address is authorized to access this page</summary>
/// <param name="addr">Client address to test</param>
/// <returns>True if address is authorized, else false</returns>
private bool CheckIPAddress(string addr)
{
// sample, just check if it's the localhost address
return (addr == "127.0.0.1" || addr == "::1");
}
}
This will check if the client address is localhost (127.0.0.1 or ::1) and allow it through, blocking everything else. Adjust that as necessary.
In the OnAuthorization method, setting filterContext.Result will stop further processing. In this case I use it to show a 403 - Forbidden response. You could also use a RedirectResult or some other result object.
You can attach that to a specific method or onto your controller class:
// Put this here to apply to all pages in this controller
[IPFilter]
public class TestController : Controller
{
// Or here to only affect the index page
[IPFilter]
public ActionResult Index()
{
return View();
}
}
I want to identify the one point which is hit every time before a request goes to the controller in the webAPI. I need to put in a custom authentication at that point. I am already doing a customAuthorization but I want to tweak in some custom authentication even before it reaches the controller.
The application_Start method only gets triggered once and so I am not quite sure what is the one place where the control goes every time we put in a URL in the browser and hit enter.
Gloabal.asax has more methods, which can be overloaded and one of them is Application_BeginRequest
And here's more detailed lifecycle. Controller factory also might help you intercepting and tweeking requests.
protected void Application_BeginRequest(object sender, EventArgs e) //Not triggered with PUT
{
//your code
}
You can opt for ActionFilterAttribute of Web API. This is triggered for every request that comes in.
Execution pipeline:
Controller Constructor > ActionFilter's OnActionExecuting > Controller action > ActionFilter's OnActionExecuted
Simple ActionFilterAttribute implementation:
public class YourFilterName : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
// pre-processing
//Your authentication logic goes here - use actionContext
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var objectContent = actionExecutedContext.Response.Content as ObjectContent;
if (objectContent != null)
{
var type = objectContent.ObjectType; //type of the returned object
var value = objectContent.Value; //holding the returned value
}
Debug.WriteLine("OnActionExecuted Response " + actionExecutedContext.Response.StatusCode.ToString());
}
}
I created a custom authorization filter with some checks in it. When the check fails it is writing to a log file. The strange thing is that with every fail it writes the error text twice to the log. How to make sure it only logs the error once?
public class AuthorizationFilter : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var key = “wrong key”;
if (key != “correct key”)
{
DateTime DateTime = filterContext.HttpContext.Timestamp;
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"Logs\log.txt");
using (StreamWriter sw = File.AppendText(path))
{
sw.WriteLine(DateTime + “| error XYZ”);
}
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
Assuming you have the filter registered globally...
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new AuthorizationFilter());
filters.Add(new HandleErrorAttribute());
}
}
It will fire once when the original action is run. Then it will return 401 unauthorized. This status is caught by ASP.NET and will automatically redirect to the login page. When the login page loads, your filter runs again (and presumably fails again).
To make it stop doing this, there are a couple of options.
Inherit from AuthorizeAttribute instead of FilterAttribute, IAuthorizationFilter. Override the AuthorizeCore method and return false when the login fails. Use the AllowAnonymousAttribute attribute on your login method (and any other methods you don't want to check).
Build your own logic to either check for AllowAnonymousAttribute or a custom attribute. Here is an example of checking for an attribute within a filter.
I suggest you use the first option. The reason is that in addition to automatically gaining the functionality of the AllowAnonymousAttribute there is also some code to deal with using output caching in conjunction with authorization.
I have the following code:
CookieHeaderValue cookie = Request.Headers.GetCookies("session").FirstOrDefault();
var isAuthenticated = _userService.IsAuthenticated(cookie);
if (!isAuthenticated)
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "");
I'd like this code to execute as soon as any part of my api is called. I havn't found any good solutions or ways to do this so i thought i would ask here instead.
(what I do now is execute the code in every get/post/put/delete which is horrible).
The best place to solve this would be an authorization filter attribute. See Authentication Filters in ASP.NET Web API 2.
The subject is too broad to repeat here in its entirety, but it comes down to creating an attribute:
public class CookieAuthenticationFilterAttribute : Attribute, IAuthenticationFilter
{
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
// your cookie code
}
}
And applying it to the controller or action methods:
[YourCookieAuthentication]
But be sure to read the link.
You can use an ActionFilter or AuthorizationFilter for this purpose. These are attribute classes that you can use on specific controllers/actions or globally. So you don't need to repeat the code for every action.
See this link for details. It shows the general authentication/authorization flow in ASP.NET Web API and how you can customize it.
So i found the best solution for my problem was the following code:
public class CookieFilterAttribute : AuthorizeAttribute
{
[Inject]
public IUserService UserService { get; set; }
protected override bool IsAuthorized(HttpActionContext actionContext)
{
CookieHeaderValue cookie = actionContext.Request.Headers.GetCookies("session").FirstOrDefault();
var isAuthenticated = UserService.IsAuthenticated(cookie);
return isAuthenticated;
}
}
I'm trying to create my own RequireHttps attribute because I don't want to use the RequireHttpsAttribute from Mvc. I just want to reject non secure connections instead of forcing them to resend them using SSL. I copied a function from Microsoft web page, but for some reason, the attribute rejects both connections, http and https.
This is my code, anyone has any idea of how to fix it or another way to approach the solution?
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method, Inherited = true,
AllowMultiple = true)]
public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "HTTPS Required"
};
}
else
{
base.OnAuthorization(actionContext);
}
}
}
actionContext.Request.Method != HttpMethod.Options
This part of the conditional will reject any request that does not use the OPTIONS HTTP method. So what this conditional translates to is that it will reject any requests except OPTIONS requests made over HTTPS. This would include any GET, POST, etc.
Try leaving off the second part of the conditional:
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)