So, I'm using the ASP.NET SimpleMembershipProvider user, roles and authorization structure. This controller requires the user is logged in, so that when it acesses this page while logged off, the login page appears.
[Authorize]
public class CompanyController : Controller
{
//stuff...
}
For this page, I want restricted access only to admins, so here it goes
[Authorize(Roles = "Admin")]
public class UserManagementController : Controller
{
//fields, methods, etc...
}
But when I try to access it logged off or logged in with a user account not in the "Admin" role, it shows the login page, but I wanted a custom page telling the user that page has restricted access and he does'nt have the credentials. How to do it, I mean without having to resort to if's and redirects in every method, that would blow the point of authorization atributes.
Thanx
You can put authorization attributes on methods NOT just the class for more fine grained control.
Related
I have a role system where the Admin can create new roles and grant Read, Write, Edit and Delete rights based on the page menu (each page menu has it's own controller) then assign that role to the user.
Users can also have multiple roles but i got that covered. The only problem I am facing is this: since Role assigning can be done dynamically from within the application i can't use the [Authorize(Roles = Role.Admin)] attribute over controller actions.
Instead i was thinking of creating [Read], [Write], [Edit] and [Delete] attributes that i would put over the actions to let me know what that action is for and then allow or deny users to call that action.
Would this be the correct approach? What would i need to have in those custom attributes and how would i allow/deny user to call them? Anyone have any similar experience?
Let's say i have Home and Admin controller and Role1 and Role2 with following rights:
Would something like the above approach works? How do I define custom access for controller actions?
I want to avoid having all the actions in the database and simplify the administration on the site.
In this way administration can be done on R/W/E/D flags on controller level instead of Role1 can all Home/Index, Home/GetRecords etc... action level.
I have same requirement for my project. In my project I have to allow user to access some pages and some pages to admin. Mostly admin can access everything, but I have to block user to access some pages/Action. I did below thing.
a. Create one class to Authenticate user/admin.
b. Create one class for User and inherited that class from "ActionFilterAttribute", and override the OnActionExecuting method.
c. Create one class for Admin and did samething as mentioned in "b", but for admin.
d. put the Authentication class name above the class name or method depending on access.
see sample code(step b). I removed some code, but you can get the idea.
public class AuthenticateUser : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext context)
{
//Do session check.
if (HttpContext.Current.Session["Id"] == null || String.IsNullOrEmpty(HttpContext.Current.Session["Id"].ToString()))
{
HttpContext.Current.Response.StatusCode = HttpStatusCode.Forbidden.GetHashCode();
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult { Data = new { Status = HttpStatusCode.Forbidden, LogonRequired = true, result = "Session Expired" }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
else
{
//redirect to login page
UrlHelper oUrl = new UrlHelper(HttpContext.Current.Request.RequestContext);
string s = oUrl.Action("", "UserInfo/");
HttpContext.Current.Response.Redirect(s);
}
}
//If usertype is User then allow it. If user type is admin then return redirect.
//Redirect code if admin
UrlHelper oUrl = new UrlHelper(HttpContext.Current.Request.RequestContext);
string s = oUrl.Action("", "UserInfo/");
HttpContext.Current.Response.Redirect(s);
}
}
I would define it differently, just give each user a set of roles.
Define each user with more than one role, if someone has access to write, you set a role for him to write, if he can also read you can add another role - read.
Now when you define your actions you can use this format[Authorize(Roles = Role.Read)]. Set each action by the access right it requires and you are done.
Use claims. each public controller action claim that you need to define.
Then have your roles defined by the list of claims/operations they can do.
Here's a great explanation here Role-based access control (RBAC) vs. Claims-based access control (CBAC) in ASP.NET MVC
Right now I have the following website structure:
Front end: website\user1, website\user2
Back end: website\account\user1, website\account\user2
Where website\account controller has generic [Authorize] attribute applied. Which is not enough since any authorized user can access other user's backed functionality simply by going to website\account\ url (if he knows his name).
What is the best way to resolve this issue?
I have two approaches so far:
Create custom Authorize attribute, inspect controller context, extract user information from there and compare it against current authorized user in ASP.NET:
var currerntUserId = (long)System.Web.Security.Membership.GetUser().ProviderUserKey;
return ExtractCurrentUserId(filterContext) == currerntUserId;
Remove part from back-end URLs and have all users access \account. Current user information will be provided by ASP.NET framework.
How about this?
Route /website/my-account
[Authorize]
public class MyAccountController : Controller
{
public ActionResult Index()
{
var userData = System.Web.Security.Membership.GetUser();
// note you could also get this from db using this.User.Identity.Name
return View(userData);
}
}
It is much easier to control the authorization this way because we are not passing the userid to the action method via a route parameter. The only way someone can get to the backend for a particular user account is by being logged in as that user.
Reply to comments:
To answer your questions in comments about what is easier / harder / better / what my preference is, I am going to go ahead and make my final answer "It depends."
It depends on the sensitivity of the data, what things admins can do that users aren't allowed to (or vice versa), how many controller actions needed to be secured, how similar the views are for public / account / admin perspectives on the data, etc. Pretty much everything stated in your question and all the answers here are valid approaches. You can certainly do it with an ActionFilter and keep the user URL's, or you could do it directly in the action method (if there aren't a lot of them), change your URL schema, implement impersonation (or not), etc.
When you are retrieving a user's data from your datastore (most likely a database), you should only retrieve data for the username of the authenticated user. In your controller, this will give you the username of the currently authenticated user:
User.Identity.Name
So you could do something like:
return ExtractCurrentUserId(filterContext) == User.Identity.Name;
If you use Role based authentication with SimpleMembership you can do something like this and give users roles that should be able to access certain controller actions:
public class MyAccountController : Controller
{
[Authorize(Roles = "Admin")]
public ActionResult User1()
{
// do user1 work
}
[Authorize]
public ActionResult User2()
{
// do user2 work
}
}
I'm creating mvc4 application and I made a costume validation that hold (manager , Super admin,admin,user)roles
,I want to make the manager make permission for any user by picking or check mark on the page who is (super admin, admin, user) allowed to view it
I mean permission to view the page if the user is allowed to view it
You cant validate users to view certain "View" and not hide some. It doesn't work that way. If you understand the fundamental concepts of MVC, The controller process the user information and return view to the user. So if you want to validate users for the view, you have to validate the controller on user access.
if you want only admin users to access "admin" pages, then validate the admin controller by checking the logged user role.
[Authorize(Roles = "Admin")]
public ActionResult AdminController()
{
//Some process
return View(); //This returns admin view if user access this controller.
}
[Authorize(Users = "someUser")]
public ActionResult AdminProfile()
{
return View();
}
Update
If you want to authorize one user with roles or simply multiple roles, Check this
A user may have multiple roles, so if you want to restrict "Admin" users between actions, make something like a user has both "Admin" and "Technician" roles where he can access technical segment of the Admin controllers. the above link will explain how to authorize with two roles for a action.
Assume like this is my SampleController action method
public ActionResult AdminView()
{
return View()
}
If want this controller method to be called if the logged in user belongs to admin role, otherwise this method call should be blocked and the user should get an some custom unauthorized access error page.
In my asp .net mvc web application, when the user logs in, I am storing the user role in a session as a string. And whenever there is a need to validate the user role, I compare the value stored in the session against a constant say "ADMIN_ROLE". But I am writing that piece of code to check for the user role in almost every controller action method and then either return an appropriate view for the user or an unauthorized access page view if the user role is restricted for the controller action method.
I googled and read that we can use something like this.
[Authorize(Roles="admin")]
public ActionResult AdminView()
{
return View()
}
But I am not sure how the Authorize and the Roles keyword works. How when putting Roles = "Admin", is going to help in checking my user role string stored in the session, or how I can redirect a user to unauthorized page, in case the role does not match the role tagged for the action method.
As per my thinking you need to code for authorization.
public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
private readonly RoleEnum[] _acceptedRoles;
public AuthorizeAttribute(params RoleEnum[] acceptedroles)
{
_acceptedRoles = acceptedroles;
}
public AuthorizeAttribute(params bool[] allowAll)
{
if (allowAll[0])
_acceptedRoles = new RoleEnum[] { RoleEnum.Admin, RoleEnum.user};
}
public void OnAuthorization(AuthorizationContext filterContext)
{
if (SessionHelper.UserInSession == null)//user not logged in
{
FormsAuthentication.SignOut();
filterContext.Result =
new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary {{ "controller", "Home" },
{ "action", "Index" },
{ "returnUrl", filterContext.HttpContext.Request.RawUrl } });//send the user to login page with return url
return;
}
if (!_acceptedRoles.Any(acceptedRole => SessionHelper.UserInSession.UserRoles.Any(currentRole => acceptedRole == currentRole.Role)))
//allow if any of the user roles is among accepted roles. Else redirect to login page
throw new UnauthorizedAccessException();
}
}
This is also work for return URL.
As per comments, if you are using custom authentication/authorization mechanism then you need to implement custom authorize attribute where you can put custom logic to check if user has admin role or not. Something like below:
public class CustomizedAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
//check for role in session variable "ADMIN_ROLE"
//if not valid user then set
filterContext.Result = new RedirectResult(URL)
}
}
There is a small nice explanation in this link:
http://weblogs.asp.net/jgalloway/archive/2011/04/28/looking-at-how-asp-net-mvc-authorize-interacts-with-asp-net-forms-authorization.aspx
as per this:
ASP.NET MVC includes an [Authorize] attribute, which when placed on any controller actions will forbid unauthorized access. The AuthorizeAttribute allows you to specify a list of roles or users.
You can also place the AuthorizeAttribute on a controller, in which case it will apply to all actions in the controller. Attempting to access an action secured by the AuthorizeAttribute when you're not logged in will take you to a standard LogOn screen, with a link to register if you don't already have an account.
How does the [Authorize] attribute redirect me to Log On?
The AuthorizeAttribute is an ActionFilter, which means that it can execute before the associated controller action. The AuthorizeAttribute performs its main work in the OnAuthorization method, which is a standard method defined in the IAuthorizationFilter interface. Checking the MVC source code, we can see that the underlying security check is really just looking at the underlying authentication information held by the ASP.NET context:
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
If the user fails authentication, an HttpUnauthorizedResult ActionResult is returned, which produces an HTTP 401(Unauthorized) status code. If it weren’t for ASP.NET Forms Authentication, an HTTP 401 status code would be sent to the browser, which would show the browser’s default login prompt.
When user hits back button after logout, they get Document Expired. However, when user click on this message
Click Try Again to re-request the document from the website,
in browser, they are able to access an authenticated page again.
It is because the page is cached. For all secure requests you will need to manually kill the cache. You can do something like this:
public class SecurePageAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(-1));
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
filterContext.HttpContext.Response.Cache.SetNoStore();
}
}
You would then use it in your controllers like so:
[SecurePage]
public ActionResult Index() {
return View();
}
You could also annotate your entire controller or register this globally if the majority of your site is secure.
For a GET Request, you'd expect them to still see the authenticated page on "back" but not be able to interact with it (assuming you use POST for actions).
Since you're talking about a POST Request though (as it's giving you the expired message), it's possible that you're missing the [Authorize] attribute from your controller/action which will allow any unauthenticated user to access it, have you checked this?