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.
Related
i have webapi action which is decorated with customauthattribute for authorization. This attribute internally checks with db if current user has viewcustomer permissions. Does anyone know better way of handling it instead of using customattribute. may be intercepting somewhere all request and run authorization checks for user/permisson/resource he is trying to access : eg getcustomer for customer id 10. So if user doesnt have access see customer id 10 he should get 403 status.
[CheckPermission(Process.CustomerManagment,Permissions.View)]
public IHttpActionResult GetCustomer(int customerId)
{
}
You can add global filters in the configuration of your web api.
Practically in your startup class (startup.cs or webapi.config) you can call on the httpconfiguration object the following method
var config = new HttpConfiguration();
config.Filters.Add(new MyAuthFilterAttribute());
In this way it will be global for all your api calls.
You should extend the IAuthenticationFilter interface.
take a look here for documentation
webapi documentation
One option would be to create a filter that you can apply globally. For example, something like this. Yes it's horrible but gives you a start:
public class GlobalAuthoriseAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var actionName = filterContext.ActionDescriptor.ActionName;
switch (controllerName)
{
case "Home":
//All call to home controller are allowed
return;
case "Admin":
filterContext.Result = new HttpUnauthorizedResult();
return;
}
}
}
Now you can add this to your entire app in the App_Start\FilterConfig.cs file:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new GlobalAuthoriseAttribute());
}
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.
Asp.net MVC2 does redirect to login page with response 302 when authenticated user has no rights.
I would like to split into two actions
If user is not authenticated then do what it does, redirect to login page.
If user is authenticated but has no required rights then return appropriate http status code and show no rights dude page.
Is there any way to do it? Or am I doing something wrong with authorize and form authentication? Only way I can think of is by writing custom authorize attribute, which I want to avoid.
You could write custom filter attribute like this:
public class CustomAuthorizeAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User.Identity == null || !filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectResult(System.Web.Security.FormsAuthentication.LoginUrl + "?returnUrl=" +
filterContext.HttpContext.Server.UrlEncode(filterContext.HttpContext.Request.RawUrl));
}
//Check user right here
if (userNotRight)
{
filterContext.HttpContext.Response.StatusCode = 302;
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
And use it in controller:
[CustomAuthorize]
public class HomeController : Controller
{
}
You could write a custom authorize attribute and in the AuthorizeCore method if the user is not authenticated return a HttpUnauthorizedResult and if he is authenticated but not in roles perform some other action you would like. Note that if you return 401 status code the FormsAuthentication framework will eventually redirect with 302 to the login page.
As suggested in Customizing authorization in ASP.NET MVC, you could subclass the AuthorizeAttribute to intercept the authenticated-but-unauthorized scenario and replace the result with a redirect.
Implement a custom AuthorizeAttribute and add the following override. The basics is to check if user is authenticated but not authorized and then redirect to you own "Access Denied" page. Hope this helps!
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
// Check if user is authenticated and if this action requires authorization
if (filterContext.HttpContext.User.Identity.IsAuthenticated
&& filterContext.ActionDescriptor.IsDefined(typeof(AuthorizeAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AuthorizeAttribute), true))
{
List<object> attributes = new List<object>(filterContext.ActionDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true));
attributes.AddRange(filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true));
// Check all authorzation attributes
foreach (var attribute in attributes)
{
var authAttribute = attribute as AuthorizeAttribute;
if (authAttribute != null)
{
if (!filterContext.HttpContext.User.IsInRole(authAttribute.Roles))
{
// User is not authorized so redirect to our access denied error page
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "area", "" },
{ "controller", "Error" },
{ "action", "AccessDenied" }
});
break;
}
}
}
}
}
Similar to solutions suggested by #hellangle and #Andreas, I used the following code to solve this problem:
public class CustomizedAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var userAuthInfo = GetUserAuthInfo();
if (!userAuthInfo.IsAuthenticated())
{
filterContext.Result = new RedirectResult(UrlToYourLoginPage);
return;
}
if (!userAuthInfo.IsAuthorized())
{
var result = new ViewResult {ViewName = "UnAuthorized"};
result.ViewBag.Message = "Sorry! You are not authorized to do this!";
filterContext.Result = result;
}
}
}
Of course, you need to implement the user authorization information class and related methods (GetUserAuthInfo, IsAuthenticated, IsAuthorized) according to your specific needs. Also a View named 'UnAuthorized' should be put to somewhere the MVC engine can find. Then it can be used on a controller class (pointed out in #hellangle's answer) or a action method:
[CustomizedAuthorizeAttribute]
public class TargetController : Controller
{
[CustomizedAuthorizeAttribute]
public ActionResult TargetAction()
{
// Your Code
}
}
In order to provide different access control strategy for various controller classes and action methods, implements a constructor for CustomizedAuthorizeAttribute class which accepts parameter(s) representing access control information and then Instantiate CustomizedAuthorizeAttribute class accordingly.
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; }
}
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" };
}
}