how activate SSL only for login and specific pages MVC - asp.net-mvc

I have a MVC 4 Web Application where I am requiring SSL for a specific set of Actions and now I'd like that also the Login process is protected by SSL.
After setting everything up, what happens is that, since the redirectToUrl parameter of the Login page is not specifying the schema, all the pages requiring login get redirected to https, regardless the [RequireHttps] attribute I have set (or better to say not set) on the Actions.
Since the pages I have not decorated with the RequireHttps attribute host mixed content, this is triggering the usual browser warnings, which is confusing for the user and I'd like to avoid.
Is there a way to fix this issue? I thought of getting the schema from the login action, but I could not find a reference to the original request apart from the returnUrl parameter which is just a relative path.
The reference I have found in SO is creating a custom attribute to decorate every Action not requiring https, but is there anything more DRY than this?

I've found the following useful, rather than decorating with [RequireHttps] I decorate with [Secure] and then this attribute does the work for me.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MyNameSpace.Attributes
{
public class SecureAttribute : ActionFilterAttribute
{
#region Variables and Properties
public bool PermanentRedirect { get; set; }
#endregion
#region Public Methods
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Cache for efficiency
var request = filterContext.HttpContext.Request;
var response = filterContext.HttpContext.Response;
// Make sure we're not in https or local
if (!request.IsSecureConnection)
{
string redirectUrl = request.Url.ToString().Replace(
Uri.UriSchemeHttp,
Uri.UriSchemeHttps);
if (PermanentRedirect)
{
// Set the status code and text description to redirect permanently
response.StatusCode = 301;
response.StatusDescription = "Moved Permanently";
}
else
{
// Set the status code and text description to redirect temporary (found)
response.StatusCode = 302;
response.StatusDescription = "Found";
}
// Add the location header to do the redirect
response.AddHeader("Location", redirectUrl);
}
base.OnActionExecuting(filterContext);
}
#endregion
}
}

Well,
I finally opted for the solution described in the comments to my original post, which proved the most painless approach.
Just to summarize (all the credit to Luke Sampsons for the code, I am just reposting here for quick reference) this is basically the code:
public class ExitHttpsIfNotRequiredAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// abort if it's not a secure connection
if (!filterContext.HttpContext.Request.IsSecureConnection) return;
// abort if a [RequireHttps] attribute is applied to controller or action
if (filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), true).Length > 0) return;
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), true).Length > 0) return;
// abort if a [RetainHttps] attribute is applied to controller or action
if (filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(RetainHttpsAttribute), true).Length > 0) return;
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(RetainHttpsAttribute), true).Length > 0) return;
// abort if it's not a GET request - we don't want to be redirecting on a form post
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) return;
// redirect to HTTP
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
public class RetainHttpsAttribute:FilterAttribute{}
The ExitHttpsIfNotRequired attribute can be used to decorate a base controller class used to derive all the controllers in the Web Application.

Related

ReturnUrl parameter in Login url

I'm developing an application using MVC 5. I have written code for login functionality. When I tried to launch application, Login page is getting added with a query string parameter ReturnUrl. Here is my code:
public ActionResult Login()
{
var authentication = Authentication;
if (Request.HttpMethod == "POST")
{
//code for user validation
}
return View();
}
I'm unable to find the code that is adding ReturnUrl parameter to url. Can anyone help me, where I can find code that adds ReturUrl parameter?
By default, AuthorizeAttribute class is part of System.Web.Mvc namespace (see Github repository: aspnetwebstack). The method leads to login redirection there is HandleUnauthorizedRequest:
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
filterContext.Result = new HttpUnauthorizedResult();
}
HTTP 401 status code response from method above will trigger FormsAuthenticationModule (see reference below), where OnLeave method redirects to login URL with FormsAuthentication.ReturnUrlVar property included:
strRedirect = loginUrl + "?" + FormsAuthentication.ReturnUrlVar + "=" + HttpUtility.UrlEncode(strUrl, context.Request.ContentEncoding);
// Do the redirect
context.Response.Redirect(strRedirect, false);
To override this behavior (including remove ReturnUrl part), create an authorization class extends from AuthorizeAttribute class, e.g. (this is an example implementation):
using System.Web.Mvc;
using System.Web.Routing;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
// #Override
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(
new { controller = "Account",
action = "Login"
}));
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
Then, you may implement custom authorization attribute like this one:
[CustomAuthorizeAttribute]
public ActionResult UserPage()
{
return View();
}
NB: Use AuthorizeAttribute on all pages that requires user login authentication, for login page use AllowAnonymousAttribute instead.
Related references:
System.Web.Security.FormsAuthenticationModule (MS Github reference)
What initially sets the ReturnUrl parameter when using AuthorizeAttribute
Generate a return Url with a custom AuthorizeAttribute
How to remove returnurl from url?
It's a default behavior of asp.net authentication. The "returnUrl" is added when you try to access a private url. If you want to remove that you will need a custom implementation of authorize class.

Changing the view in an ASP.NET MVC Filter

I want to redirect the user to a different view if they are using a mobile browser. I've decided I'd like to do this using MVC filters by applying it to actions which I want to have a mobile view.
I believe this redirect needs to happen in OnActionExecuted, however the filterContext does not contain information on the view - it does, however in OnResultExecuted, but by this time I believe it is too late to change the view.
How can I intercept the view name and change the ViewResult?
This is what I have in the result executed and what I'd like to have work in Action Executed.
public class MobilePageFilter : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.Result is ViewResult)
{
if (isMobileSite(filterContext.HttpContext.Session[SetMobile.SESSION_USE_MOBILE]))
{
ViewResult viewResult = (ViewResult)filterContext.Result;
string viewName = viewResult.ViewName;
filterContext.Result = new ViewResult
{
ViewName = "Mobile/" + viewName,
ViewData = viewResult.ViewData,
TempData = viewResult.TempData
};
}
}
base.OnResultExecuted(filterContext);
}
}
I would recommend you the following blog post which explains a better alternative to achieve what you are asking for rather than using action filters.
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!

using ssl with aspnet mvc creates a problem when posting data on to the page which is secure

I know there are many posts regarding and it works fine if the action method which i am calling is just a get method, but it fails if the action method has a 'Post' attribute because in the code we do a redirect to the url and not a post
I am using code below...
using System;
using System.Web;
using System.Web.Mvc;
namespace Helpers
{
public class RequiresSSL : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase req = filterContext.HttpContext.Request;
HttpResponseBase res = filterContext.HttpContext.Response;
//Check if we're secure or not and if we're on the local box
if (!req.IsSecureConnection && !req.IsLocal)
{
var builder = new UriBuilder(req.Url)
{
Scheme = Uri.UriSchemeHttps,
Port = 443
};
res.Redirect(builder.Uri.ToString());
}
base.OnActionExecuting(filterContext);
}
}
}
If you haven't figured this out yet [RequireHttps] is baked into the framework. Place that attribute on the method you're intending to be secure.

ASP.NET MVC Futures RequireSSL Attribute and Authorize Attribute Together

Is anyone successfully using both the Authorize and RequireSSL (from MVC futures) attributes together on a controller? I have created a controller for which I must enforce the rule that the user must be logged in and using a secure connection in order to execute. If the user is not on a secure connection, I want the app to redirect to https, thus I am using Redirect=true on the RequireSSL attribute. The code looks something like (CheckPasswordExpired is my homegrown attribute):
[Authorize]
[RequireSsl(Redirect = true)]
[CheckPasswordExpired(ActionName = "ChangePassword",
ControllerName = "Account")]
[HandleError]
public class ActionsController : Controller
{
....
}
mysite.com/Actions/Index is the default route for the site and also the default page to redirect to for forms authentication.
When I browse to http://mysite.com, what I want to get is the user redirected to a secure connection, and because they are not authenticated yet, to the login page. What I get is an HTTP 400 error (Bad Request). If I navigate to http://mysite.com/Account/Login, the redirect works, but neither my Account controller nor Login action method have the [Authorize] attribute.
Anyone have any experience with using these two attributes together to achieve my objective?
Thanks!
I'm using both of them with success. Do you have the attributes on your default action?
public class HomeController : BaseController
{
[Authorize]
[RequireSsl]
public ActionResult Index ()
{
}
}
BTW I'm using a slightly modified version than the futures so that I can disable SSL globally:
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class RequireSslAttribute : FilterAttribute, IAuthorizationFilter
{
public RequireSslAttribute ()
{
Redirect = true;
}
public bool Redirect { get; set; }
public void OnAuthorization (AuthorizationContext filterContext)
{
Validate.IsNotNull (filterContext, "filterContext");
if (!Enable)
{
return;
}
if (!filterContext.HttpContext.Request.IsSecureConnection)
{
// request is not SSL-protected, so throw or redirect
if (Redirect)
{
// form new URL
UriBuilder builder = new UriBuilder
{
Scheme = "https",
Host = filterContext.HttpContext.Request.Url.Host,
// use the RawUrl since it works with URL Rewriting
Path = filterContext.HttpContext.Request.RawUrl
};
filterContext.Result = new RedirectResult (builder.ToString ());
}
else
{
throw new HttpException ((int)HttpStatusCode.Forbidden, "Access forbidden. The requested resource requires an SSL connection.");
}
}
}
public static bool Enable { get; set; }
}

Setting result for IAuthorizationFilter

I am looking to set the result action from a failed IAuthorizationFilter. However I am unsure how to create an ActionResult from inside the Filter. The controller doesn't seem to be accible from inside the filter so my usual View("SomeView") isn't working. Is there a way to get the controler or else another way of creating an actionresult as it doesn't appear to be instantiable?
Doesn't work:
[AttributeUsage(AttributeTargets.Method)]
public sealed class RequiresAuthenticationAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext context)
{
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
context.Result = View("User/Login");
}
}
}
You should look at the implementation of IAuthorizationFilter that comes with the MVC framework, AuthorizeAttribute. If you are using forms authentication, there's no need for you to set the result to User/Login. You can raise a 401 HTTP status response and ASP.NET Will redirect to the login page for you.
The one issue with setting the result to user/login is that the user's address bar is not updated, so they will be on the login page, but the URL won't match. For some people, this is not an issue. But some people want their site's URL to correspond to what the user sees in their browser.
You can instantiate the appropriate ActionResult directly, then set it on the context. For example:
public void OnAuthorization(AuthorizationContext context)
{
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
context.Result = new ViewResult { ViewName = "Whatever" };
}
}

Resources