MVC Reroute to action when user isn't authorized - asp.net-mvc

I'm currently working on setting up permissions for my web app. Not everyone needs to have access to certain pages, such as creating/editing/deleting (the usually) or adding new rights to users. I have a table in my database that keeps track of the users and their role/rights. I am overriding the AuthorizeAttribute. What I would like to happen is when the user is not authorized to access a page is for them to be redirected back to the page they were just at and a alert show saying they don't have access.
Example: If they are on the Home Page and click the Add New Something Button, if they don't have rights they will be directed back to the Home Page with the error.
To make this work I need to get access to the previous action/controller names since the previous page may never be the same.
Current custom AuthorizeAttribute HandleUnauthorizedRequest Method
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "action", filterContext.RouteData.Values["action"] },
{ "controller", filterContext.RouteData.Values["controller"] }
});
}
This gets me the action/controller they are trying to access, am I able to get where they are coming from?

Using Klings and Stephen.vakil advice I have:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
Uri requestUrl = filterContext.HttpContext.Request.UrlReferrer;
if (requestUrl != null)
{
filterContext.Result = new RedirectResult(requestUrl.ToString());
}
else
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "action", "NotAuthorized" },
{ "controller", "Admin" }
});
}
}
If there was a referrer then take them back to that page, but I still need a popup to appear when they reach this page. I know that it can be done in each individual view with javascript and the alert(), but I am looking for something that can be done in one place without adjusting the other views if possible. If not, send to a generic page stating they aren't authorized.

Related

Redirect into a brand new page

I have an attribute, SessionEndAttribute, that checks if the session is alive. It is attached to each action.
If the session has expired, it runs this method:
public override void OnActionExecuting(ActionExecutingContext filterContext) {
// do logout, clear cookies
filterContext.Result = new RedirectToRouteResult( .. )
}
But sometimes the response is not a route to a new page (e.g. route "/Account/Login") but just a popup or partial view, which could just be a container div.
So, the question is - how can I always redirect to a new page?
You have to tell this particular actionfilter to only run for full controller actions and not partial controller actions [ChildActionOnly].
You also have to make sure that all of your AJAX calls use the [ChildActionOnly] calls.
there should be a boolean property in the context, IsChildAction to help you out.
As Andriy pointed out in the comment, you should also check and make sure the filterContext isn't executing an AJAX request, using ifilterContext.HttpContext.Request.IsAjaxRequest()
Usage:
public override void OnActionExecuting(ActionExecutingContext filterContext) {
// do logout, clear cookies
if (!filterContext.IsChildAction && !filterContext.HttpContext.Request.IsAjaxRequest()) {
filterContext.Result = new RedirectToRouteResult( .. )
}
}

Can I show a message on my Login page explaining that authorization had failed?

I'm using a custom authorization scheme, and when a user isn't authorized, I return an HttpUnauthorizedResult. This causes the user to be redirected to the login page. Is it somehow possible, in the login page, to detect that it is being used because of an authorization failure and tell the user this? If so, how could I do this?
It would be a bonus if I could tell the user, "You need to log in as a user with x role to perform the action you requested", or something like that.
Rather than return an HTTP 401, return a web page with the message you want, and a button to go to the login page.
Actually, you think that you are sending an Unauthorized response, but in reality ASP.NET is intercepting that HTTP 401 response and sending an HTTP 302 (Redirection) to your login page instead. So if you want a custom message, just redirect yourself to the page you want.
Cheers.
UPDATE:
If you create your own Authorize filter, you can define what happen if the user is not authorized/authenticated:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
readonly String _customError;
public MyAuthorizeAttribute(String customError)
{
_customError = customError;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
FormsAuthentication.SignOut();
filterContext.Controller.TempData["Error"] = _customError;
filterContext.Result = new RedirectResult("~/Account/yourErrorView");
}
}
(Not tested)
That way you can use your attribute this way:
[MyAuthorize("You are not authorized to see this thing")]
public ActionResult MyActionMethod()
{
return View();
}
And then the user will be redirected to "~/Account/yourErrorView", and in the TempData you will find the custom error message.
Cheers.
I think it would be better pass additional parameter which will describe the cause of error, for example:
/Account/Login?error=4
and in the Login action check if error exists.
Besides you can store your error messages in different ways: session, cookie.
Use ActionFilterAttribute instead of the AuthorizeFilterAttribute to point it to your error handling page.
public class RoleAuthorize: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = (YourController)filterContext.Controller;
try
{
if (!controller.CheckForRoleMethod())
{
throw new System.Security.SecurityException("Not Authorized!");
}
}
catch (System.Security.SecurityException secEx)
{
if (secEx != null)
{
// I use TempData for errors like these. It's just me.
TempData["ErrorMessage"] = secEx.Message;
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "ErrorHandler" }, { "action", "Error" } });
}
}
}
base.OnActionExecuting(filterContext);
}
You have to make a separate method on that controller being decorated to check if the cached user is Authorized or not like so:
public class ApplicationController : Controller
{
public bool CheckForRoleMethod(){
// get formsauthentication details to retrieve credentials
// return true if user has role else false
}
}

presenting two different login screens based on roles

I was trying to figure out whether it would be possible to present two different login screens based on the authorization role. The requirement is simple. I have two roles say "admin" and "public". There are "Authorize" attributes marked all over my applications action methods for these two roles.
Now the requirements of my application specifies different login screens for "admin" and "public". The "admin" login screen is secured by an additional security code which is not required for "public" login screen. What I was looking for is some way to know who is trying to log in based on the Action method invoked. If the action method invoked is decorated by Authorize[Roles="admin"] then I would present the admin login screen, whereas if action method invoked is applied Authorize[Roles="public"] then I need to present the public login screen.
If the Login Screen is directly invoked then by default the public login screen would be presented.
It may sound a little weird but this is the scenario I am trying to figure out the solution for.
You could write a custom authorize attribute which will redirect to the proper logon action:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var roles = Roles.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "account",
action = "adminlogon",
returnUrl = filterContext.HttpContext.Request.RawUrl
}));
}
else
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "account",
action = "logon",
returnUrl = filterContext.HttpContext.Request.RawUrl
}));
}
}
}
and then decorate your controllers/actions with it:
[MyAuthorize(Roles = "admin")]
public ActionResult Foo()
{
return View();
}
[MyAuthorize(Roles = "normaluser")]
public ActionResult Bar()
{
return View();
}
Now if a non authenticated user tries to hit the Foo action he will be redirected to the /account/adminlogon action and if he tries to hit the Bar action he would be redirected to the /account/logon action. In both cases the current url will be passed as returnUrl parameter so that upon successful login the user could be brought to the page that he was initially trying to browse.

ASP.NET MVC 3 determine session status (new or timeout)

I currently use the default forms authentication method for my ASP.NET MVC application.
above all of my actionmethods that require authentication I have this attribute
[Authorize()]
When someone tries to call the page that that action method "serves" and they haven't yet logged in, it sends them to the login page...perfect! However, if their session times out and they try to hit that page, they're also just redirected to the login page with no indication of why. I'd like to be able to determine if it's a new visit, or if it's a timeout and display a different message on the login screen accordingly.
Is that possible?
Have a look at this custom authorize attribute i have made. It was to implement some custom role based authorization, but you could make it work for you as well. There is a Session.IsNewSession property you can check to see if this request takes place on a new session.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.User.Identity.IsAuthenticated)
{
httpContext.User = new GenericPrincipal(httpContext.User.Identity, AdminUserViewModel.Current.SecurityGroups.Select(x => x.Name).ToArray());
}
return base.AuthorizeCore(httpContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectResult("/Authentication/NotAuthorized", false);
}
else
{
if (filterContext.HttpContext.Session.IsNewSession)
{
// Do Something For A New Session
}
base.HandleUnauthorizedRequest(filterContext);
}
}
}
On sign-in, you can set a cookie that's tied to the browser session. If that cookie exists, you know that the session timed out. If not, you know it's a new visit.

ASP MVC: Getting confused when implementing ASP MVC Security

I am implementing the security part of an ASP MVC application and I am getting confused in how to implement a custom membership provider since there seems to be a lot of functionallity that I wouldn't use.
The application I'm porting usually managed the security through an object stored in session called "SecurityManager" which contained the current user and a form permission collection. Each item of this collection contains the permission per form of the logged user and permissions per field of that form (in case they were needed if not full control of the form is assumed).
However, when I see the methods of the MembershipProvider and the AuthorizeAttribute tag they assume that I will use roles which my application does not use, we just have permission groups which are just the permissions grouped together for certain user groups, but they tend to change in time.
So basically the only thing I would need would be something that when a request is made would check if the security manager is stored in session (if it is not the user is not authenticated and will be redirected to the login page) and then get that object from session and perform operation with it to know if the user can or cannot access the view.
What would be the best approach for this? I've read that bypassing the custom membership was not a good idea.
Update: I recently came up against this and figured out how to use the AuthorizeAttribute to do exactly as you wanted. My attribute, that verifies if the user is an administrator, works as follows:
public class AuthorizeAdminAttribute : AuthorizeAttribute
{
public bool IsValidUser { get; protected set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) { throw new ArgumentNullException("httpContext"); }
// Make sure Forms authentication shows the user as authenticated
if (httpContext.User.Identity.IsAuthenticated == false) return false;
// Retrieve the unit of work from Windsor, and determine if the current user is an admin
var unitOfWork = Bootstrapper.WindsorContainer.Resolve<IUnitOfWork>();
var user = new UserByIdQuery(unitOfWork).WithUserId((int)Membership.GetUser().ProviderUserKey).Execute();
if (user == null)
return false;
// Otherwise the logged in user is a real user in the system
IsValidUser = true;
return user.IsAdmin;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext == null) { throw new ArgumentNullException("filterContext"); }
// If this user is a valid user but not an admin, redirect to the homepage
if (IsValidUser)
{
// Redirect them to the homepage
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{ "area", "" },
{ "action", "Index" },
{ "controller", "Home" }
});
}
else
{
// User isn't logged in, perform normal operations
base.HandleUnauthorizedRequest(filterContext);
}
}
}
Essentially you have to user the AuthorizeCore() to determine if the user is logged in, store that result, and then perform authorization for the roles in your system. Then in your HandleUnauthorizedRequest you have to figure out if the request was unauthorized because the user wasn't logged in, or if it was because they weren't authorized.
Old Answer
I accomplish using the Authorize attribute by creating a subclass of the AuthorizeAttribute class. So for example:
public class MyCustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) { throw new ArgumentNullException("httpContext"); }
// Make sure Forms authentication shows the user as authenticated
if (httpContext.User.Identity.IsAuthenticated == false) return false;
// replace with whatever code you need to call to determine if the user is authorized
return IsUserAuthorized();
}
}
Now when a controller or action is called, and is decorated with [MyCustomAuthorize] it will run this code to determine if the user is authorized based on your custom logic, and if not will redirect them exactly how the [Authorize] attribute would.
I don't know if this is the best approach, but it is what I came up with.

Resources