I have a controller in ASP.NET MVC that I've restricted to the admin role:
[Authorize(Roles = "Admin")]
public class TestController : Controller
{
...
If a user who is not in the Admin role navigates to this controller they are greeted with a blank screen.
What I would like to do is redirect them to View that says "you need to be in the Admin role to be able to access this resource."
One way of doing this that I've thought of is to have a check in each action method on IsUserInRole() and if not in role then return this informational view. However, I'd have to put that in each Action which breaks the DRY principal and is obviously cumbersome to maintain.
This question is related to
c#
asp.net-mvc
redirect
authorization
The code by "tvanfosson" was giving me "Error executing Child Request".. I have changed the OnAuthorization like this:
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (!_isAuthorized)
{
filterContext.Result = new HttpUnauthorizedResult();
}
else if (filterContext.HttpContext.User.IsInRole("Administrator") || filterContext.HttpContext.User.IsInRole("User") || filterContext.HttpContext.User.IsInRole("Manager"))
{
// is authenticated and is in one of the roles
SetCachePolicy(filterContext);
}
else
{
filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page.");
filterContext.Result = new RedirectResult("~/Error");
}
}
This works well and I show the TempData on error page. Thanks to "tvanfosson" for the code snippet. I am using windows authentication and _isAuthorized is nothing but HttpContext.User.Identity.IsAuthenticated...
This problem has hounded me for some days now, so on finding the answer that affirmatively works with tvanfosson's answer above, I thought it would be worthwhile to emphasize the core part of the answer, and address some related catch ya's.
The core answer is this, sweet and simple:
filterContext.Result = new HttpUnauthorizedResult();
In my case I inherit from a base controller, so in each controller that inherits from it I override OnAuthorize:
protected override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
YourAuth(filterContext); // do your own authorization logic here
}
The problem was that in 'YourAuth', I tried two things that I thought would not only work, but would also immediately terminate the request. Well, that is not how it works. So first, the two things that DO NOT work, unexpectedly:
filterContext.RequestContext.HttpContext.Response.Redirect("/Login"); // doesn't work!
FormsAuthentication.RedirectToLoginPage(); // doesn't work!
Not only do those not work, they don't end the request either. Which means the following:
if (!success) {
filterContext.Result = new HttpUnauthorizedResult();
}
DoMoreStuffNowThatYouThinkYourAuthorized();
Well, even with the correct answer above, the flow of logic still continues! You will still hit DoMoreStuff... within OnAuthorize. So keep that in mind (DoMore... should be in an else therefore).
But with the correct answer, while OnAuthorize flow of logic continues till the end still, after that you really do get what you expect: a redirect to your login page (if you have one set in Forms auth in your webconfig).
But unexpectedly, 1) Response.Redirect("/Login") does not work: the Action method still gets called, and 2) FormsAuthentication.RedirectToLoginPage(); does the same thing: the Action method still gets called!
Which seems totally wrong to me, particularly with the latter: who would have thought that FormsAuthentication.RedirectToLoginPage does not end the request, or do the equivalant above of what filterContext.Result = new HttpUnauthorizedResult() does?
In your Startup.Auth.cs file add this line:
LoginPath = new PathString("/Account/Login"),
Example:
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
Would have left this as a comment but I need more rep, anyways I just wanted to mention to Nicholas Peterson that perhaps passing the second argument to the Redirect call to tell it to end the response would have worked. Not the most graceful way to handle this but it does in fact work.
So
filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);
instead of
filterContext.RequestContext.HttpContext.Response.Redirect("/Login);
So you'd have this in your controller:
protected override void OnAuthorization(AuthorizationContext filterContext)
{
if(!User.IsInRole("Admin")
{
base.OnAuthorization(filterContext);
filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);
}
}
I had the same issue. Rather than figure out the MVC code, I opted for a cheap hack that seems to work. In my Global.asax class:
member x.Application_EndRequest() =
if x.Response.StatusCode = 401 then
let redir = "?redirectUrl=" + Uri.EscapeDataString x.Request.Url.PathAndQuery
if x.Request.Url.LocalPath.ToLowerInvariant().Contains("admin") then
x.Response.Redirect("/Login/Admin/" + redir)
else
x.Response.Redirect("/Login/Login/" + redir)
You can work with the overridable HandleUnauthorizedRequest
inside your custom AuthorizeAttribute
Like this:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Returns HTTP 401 by default - see HttpUnauthorizedResult.cs.
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "YourActionName" },
{ "controller", "YourControllerName" },
{ "parameterName", "YourParameterValue" }
});
}
You can also do something like this:
private class RedirectController : Controller
{
public ActionResult RedirectToSomewhere()
{
return RedirectToAction("Action", "Controller");
}
}
Now you can use it in your HandleUnauthorizedRequest
method this way:
filterContext.Result = (new RedirectController()).RedirectToSomewhere();
Perhaps you get a blank page when you run from Visual Studio under development server using Windows authentication (previous topic).
If you deploy to IIS you can configure custom error pages for specific status codes, in this case 401. Add httpErrors under system.webServer:
<httpErrors>
<remove statusCode="401" />
<error statusCode="401" path="/yourapp/error/unauthorized" responseMode="Redirect" />
</httpErrors>
Then create ErrorController.Unauthorized method and corresponding custom view.
You should build your own Authorize-filter attribute.
Here's mine to study ;)
Public Class RequiresRoleAttribute : Inherits ActionFilterAttribute
Private _role As String
Public Property Role() As String
Get
Return Me._role
End Get
Set(ByVal value As String)
Me._role = value
End Set
End Property
Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
If Not String.IsNullOrEmpty(Me.Role) Then
If Not filterContext.HttpContext.User.Identity.IsAuthenticated Then
Dim redirectOnSuccess As String = filterContext.HttpContext.Request.Url.AbsolutePath
Dim redirectUrl As String = String.Format("?ReturnUrl={0}", redirectOnSuccess)
Dim loginUrl As String = FormsAuthentication.LoginUrl + redirectUrl
filterContext.HttpContext.Response.Redirect(loginUrl, True)
Else
Dim hasAccess As Boolean = filterContext.HttpContext.User.IsInRole(Me.Role)
If Not hasAccess Then
Throw New UnauthorizedAccessException("You don't have access to this page. Only " & Me.Role & " can view this page.")
End If
End If
Else
Throw New InvalidOperationException("No Role Specified")
End If
End Sub
End Class
Source: Stackoverflow.com