I'm looking to secure different areas of my MVC application to prevent standard user's from accessing admin type views. Currently, if any user is logged in and they attempt to view the About page (out of the box template in visual studio), it will simply redirect them to the login page. I'd prefer the user is informed that they do not have permission to view the page.
[Authorize(Roles="Admin")]
public ActionResult About()
{
return View();
}
It seems redundant to send an already authenticated user to the login page when they don't have permission.
Here is an attribute that I've created that can be used to direct to an unauthorized security action. it also allows you to specify a Reason which will be passed to the Unauthorized action on the Security controller, which you can then use for the view.
You can create any number of properties to customize this to fit your particular application, just make sure to add it to the RouteValueDictionary.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public sealed class ApplySecurityAttribute : ActionFilterAttribute
{
private readonly Permission _permission;
public ApplySecurityAttribute(Permission permission)
: this(permission, string.Empty) {}
public ApplySecurityAttribute(Permission permission, string reason)
{
_permission = permission
Reason = reason;
}
public string Reason { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!PermissionsManager.HasPermission(_permission)) // Put security check here
{
var routeValueDictionary = new RouteValueDictionary
{
{ "controller", "Security" }, // Security Controller
{ "action", "Unauthorized" }, // Unauthorized Action
{ "reason", Reason } // Put the reason here
};
filterContext.Result = new RedirectToRouteResult(routeValueDictionary);
}
base.OnActionExecuting(filterContext);
}
}
Here is the security controller
public class SecurityController : Controller
{
public ViewResult Unauthorized(string reason)
{
var vm = new UnauthorizedViewModel { Reason = reason };
return View(vm);
}
}
Here is the attribute declaration on a controller you wish to secure
[ApplySecurity(Permission.CanNuke, Reason = "You are not authorized to nuke!")]
Here is how PermissionsManager does the check to see if the user has the permissions
public static class PermissionsManager
{
public static bool HasPermission(EZTracPermission permission)
{
return HttpContext.Current.GetCurrentUser().Can(permission);
}
}
Related
In ASP.NET MVC Identity,the relations data for Users and Roles is saved in AspNetUserRoles table, this table has two field:UserId,RoleId, but i want to add other fields to this table, such as department field.
So if an user logins in different departments,he will have different roles.
Anyone knows how to do it? Thanks in advance!
I Would Suggest you investigate ASPNet User Claims. You can assign different claims to a user with the identity manager, and based on the claim type of the user you will allow him access or not. Create a custom Claims Attribute which will be placed on top of the various controller to authenticate the user. this must be implemented based on your needs. the custom attribute will then fire before the controller gets executed and if the uses is allowed he will pass. else return to error page of you choice.
Sample Attribute usage
[ClaimsAuthorize(ClaimsData.EditAddress)]
public ActionResult CitiesPartial()
Attribute Authentication
public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _claimType;
public ClaimsAuthorizeAttribute(string type)
{
_claimType = type;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var user = (ClaimsPrincipal)HttpContext.Current.User;
if (user.HasClaim(_claimType, "True"))
{
base.OnAuthorization(filterContext);
}
else
{
HandleUnauthorizedRequest(filterContext, _claimType + " Not Allowed ");
}
}
protected void HandleUnauthorizedRequest(AuthorizationContext filterContext, string message)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "ClaimNotAuthorized" },
{ "controller", "Home" },
{"errorMessage", message }
});
}
public static bool AuthorizedFor(string claimType)
{
var user = (ClaimsPrincipal)HttpContext.Current.User;
return user.HasClaim(claimType, "True");
}
}
hope this helps.
I am trying to lock users that are authenticated but not active in their profile page (specific controller/action). By active, I mean that the authorization process needs to check the db to see if the account is active (just a column with boolean data).
This is what I have tried:
However this is getting into a loop.
The idea is:
if authenticated and active = show the controller/action.
if not authenticated = show login page (forms auth)
if authenticated and not active = show profile page.
public class CustomAuthorize : AuthorizeAttribute
{
private ISADietRepository repository;
public CustomAuthorize()
{
this.repository = new SADietRepository(new SADietEntities());
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
//base.OnAuthorization(filterContext);
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
string user = filterContext.HttpContext.User.Identity.Name;
var result = repository.GetVetClinicByEmail(user);
if (!result.IsActive)
{
filterContext.Result =
new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary{{"Controller", "SADiet"},
{"Action", "NewCustomer"},
});
}
}
else
{
filterContext.Result =
new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary{{"Controller", "SADiet"},
{"Action", "Login"},
});
}
base.OnAuthorization(filterContext);
}
}
Guys I just tried this code below. It works as long as the user is authenticated. If the user is not authenticated, it no longer asks for authentication, it just run the action with no user logged in.
public class CustomAuthorize : ActionFilterAttribute
{
private ISADietRepository repository;
public CustomAuthorize()
{
this.repository = new SADietRepository(new SADietEntities());
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
string user = filterContext.HttpContext.User.Identity.Name;
var result = repository.GetVetClinicByEmail(user);
if (!result.IsActive)
{
filterContext.Result =
new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary{{"Controller", "MyAccount"},
{"Action", "Profile"},
});
}
}
base.OnActionExecuting(filterContext);
}
}
I have found the problem.
My second code is correct. The problem is that I replaced the attribute instead of adding the new one to decorate the controller.
I was doing this:
[CustomAuthorize]
public class SADietController : Controller
instead of this:
[CustomAuthorize, Authorize]
public class SADietController : Controller
all sorted!
Pls find the link below that will help to resolve your issue.
ASP.NET MVC 4 custom Authorize attribute - How to redirect unauthorized users to error page?
ASP.NET MVC - How to show unauthorized error on login page?
http://prideparrot.com/blog/archive/2012/6/customizing_authorize_attribute
Custom Authorize Attribute
http://www.tsjensen.com/blog/post/2010/10/16/ASPNET+MVC+Custom+Authorize+Attribute+With+Roles+Parser.aspx
http://mvcsitemap.codeplex.com/workitem/6333
http://code.commongroove.com/2012/04/20/asp-net-mvc-simple-custom-authorization-by-inheriting-from-the-authorizeattribute/
Implementing a basic authorization and authentication layer is quite easy with ASP.NET MVC 4; it's all automatically generated with the 'ASP.NET MVC 4 Web Application'-project template.
However, I'm tasked with implementing some controller actions that require re-authentication and I'm aiming for a maintainable solution. Simply put in a user story, I'm trying to implement the following:
User logs on;
User navigates to a controller (attributed with [Authorize]) action which renders a form view;
User performs a POST by submitting the form;
An authentication form appears in which the user needs to re-authenticate using his/her username and password;
If authentication is succesfull, proceed with handling the POST-request.
Note that 'reauthentication' does not have to alter the state of the current user session.
Obviously, there are many ways to implementing this, but I feel like an implementation which looks similiar to the following (pseudo) sample would suit my needs.
[Authorize]
[InitializeSimpleMembership]
public class SpecialActionController : Controller
{
public ActionResult SpecialForm() { return View(); }
public ActionResult Succes() { return View(); }
[HttpPost]
[ReAuthenticate] /* <- Prompts user with reauthentication form before proceeding. */
public ActionResult SpecialForm(SpecialFormModel model)
{
if (ModelState.IsValid)
RedirectToAction("Succes");
else
return View(model);
}
}
Any suggestions?
Edit: I forgot to mention that any OAuth-related features are out of scope. External authentication is not an issue here and does not require support. In fact, with the current project I'm working on, all OAuth-related features are either removed or deactivated.
You should be able to do this using a combination of a custom AuthorizeAttribute and the Session. Override the AuthorizeCore method and let all the default authentication take place but introduce your own extra check (for re-authentication) e.g.
public class RecurringAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var reauth = (bool?)httpContext.Session["ReAuthenticated"];
var result = base.AuthorizeCore(httpContext) && (reauth ?? false);
httpContext.Session["ReAuthenticated"] = !result;
return result;
}
}
This should re-direct the user to the login page everytime they hit the action and they haven't re-authenticated. If the user has re-authenticated, we clear the session variable to force a login on the next request.
For this to work correctly, we need a hook to set the ReAuthentication session variable - I think the LogOn method in the AccountController would be the ideal place for this
public class AccountController : Controller
{
...
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
Session["ReAuthenticated"] = User.Identity.IsAuthenticated;
return RedirectToLocal(returnUrl);
}
...
}
}
Then all that's left to do is decorate our controller actions
[Authorize]
public ActionResult SomePrivateAction()
{
...
}
[RecurringAuthorize]
public ActionResult SomeSuperSecretAction()
{
...
}
You should find authorization will work as normal for any actions using the default AuthorizeAttribute and any actions decorated with the RecurringAuthorizeAttribute will be forced to login everytime they request the page, which includes page refreshes.
I tried to implement the hypothetical [ReAuthenticate]-attribute, but found myself relying on reflection too much. After putting some thought into a more manageable solution, I finally came up with the following:
ReAuth class
public sealed class ReAuth
{
#region Constructor
private ReAuth(Func<System.Web.Mvc.ActionResult> onSuccessAction)
{
this.onSuccessAction = onSuccessAction;
}
#endregion
#region Public static members
public static ReAuth CreateFor(HttpSessionStateBase httpSession, Func<System.Web.Mvc.ActionResult> onSuccessAction)
{
httpSession[sessionKey] = new ReAuth(onSuccessAction);
return GetFor(httpSession);
}
public static ReAuth GetFor(HttpSessionStateBase httpSession)
{
return httpSession[sessionKey] as ReAuth;
}
public static bool ExistsFor(HttpSessionStateBase httpSession)
{
return httpSession[sessionKey] as ReAuth != null;
}
#endregion
#region Public instance members
public bool ReAuthenticated { get; set; }
public System.Web.Mvc.ActionResult Handle()
{
if (ReAuthenticated)
return onSuccessAction();
else
return new System.Web.Mvc.RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary
{
{ "Controller", "#" }, /* Replace '#' with the name of the controller that implements the re-authentication form... */
{ "Action", "#" } /* Replace '#' with the name of the action on the aforementioned controller. */
});
}
#endregion
#region Private members
private const string sessionKey = "reAuthenticationSessionKey";
private readonly Func<System.Web.Mvc.ActionResult> onSuccessAction;
#endregion
}
Implementation
Suppose we have a hypothetical controller where the solution is applied:
public class AccountInfoController : System.Web.Mvc.Controller
{
/* snip... */
[HttpPost]
public ActionResult EditAccountInfo(AccountInfo model)
{
if (ModelState.IsValid)
return ReAuth.CreateFor(Session, () => { return Success(); }).Handle();
else
return View(model);
}
}
...and, we need a controller (essentially, a 'dumb' copy of the real AccountController that does not tamper with the Forms Authentication User Session state) in which the re-authentication takes place.:
public class ReAuthController : System.Web.Mvc.Controller
{
/* Snip... */
[HttpPost]
public ActionResult LogOn(LogOnModel model)
{
if (ModelState.IsValid)
{
ReAuth.GetFor(Session).ReAuthenticated = Membership.ValidateUser(model.User, model.Password);
return ReAuth.Handle();
}
return View(model);
}
}
As far as I know, this is a manageable solution. It does rely a lot on storing objects into session state. (Especially the object state of the controller which implements the ReAuth-class) If anyone has additional suggestions, please let me know!
If I want only administrator to access the action called "ManagerUser", I know I can do this:
[Authorize( Roles = Constants.ROLES_ADMINISTRATOR )]
public ActionResult ManageUser( string id )
{
}
What if I want to give everyone access except to administrator? I do not want to write all roles up there on function :|.
Any recommendations/way outs?
You can create your own custom Authorize attribute, something like "AuthorizeAllExceptAdmin." Within that class you would simply need to check whether or not the current user was an admin, and if they were reject it, otherwise accept it.
Here's a good tutorial, but you'll probably end up with something like:
public class AuthorizeAllExceptAdmin : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return !httpContext.User.IsInRole(Constants.ROLES_ADMINISTRATOR);
}
}
Then your controller method becomes:
[AuthorizeAllExceptAdmin]
public ActionResult SomethingOnlyNonAdminsCanDo()
{
}
Here's an example of the custom attribute that takes in roles to deny.
public class DoNotAuthorize : AuthorizeAttribute
{
private IEnumerable<string> _rolesToReject;
public DoNotAuthorize(IEnumerable<string> rolesToReject)
{
_rolesToReject = rolesToReject;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
foreach (var role in _rolesToReject)
{
if (httpContext.User.IsInRole(role))
return false;
}
return true;
}
}
Then your controller method becomes:
[DoNotAuthorize(new [] {Constants.ROLES_ADMINISTRATOR})]
public ActionResult SomethingOnlyNonAdminsCanDo()
{
}
I would put some thought into it before choosing one of the above options. If you think you'll have several methods (or entire controllers) with similar authorization requirements (i.e, several actions an admin can not perform) then I would stick with the non-parameterized custom attribute. This way, you can evolve them all together (by only changing the custom attribute) later on. For example, maybe later on you want admins to be able to go into a special mode where they can perform these actions.
Alternatively, if the autorization is more varied amongst the actions, then using the parameterized list makes sense, since they'll evolve relatively independently.
Besides creating a custom AuthorizeAttribute, suggested by manu, you could use PrincipalPermission, with a Deny-SecurityAction:
[PrincipalPermission(SecurityAction.Deny, Role="Administrator")]
In my app I don't use roles so I have to query the database to determine whether the user has access or not. The benefits of the code below is that you can redirect the user to a certain action very easily. I explained the code in my blog post at http://blog.athe.la/2009/12/implementing-permission-via-windows-authentication-in-asp-mvc-using-action-filters/
public class DatabaseRepository()
{
private readonly DatabaseDataContext db = new DatabaseDataContext();
public bool UserHasPermission(string userLogon) {
return (from permission this.db.Permissions
where permission.HasPermissionSw == true
select permission).Contains(userLogon);
}
}
public class UserHasPermission: ActionFilterAttribute
{
private readonly DatabaseRepository databaseRepository = new DatabaseRepository();
private readonly string redirectAction;
public UserHasPermission(string redirectTo)
{
this.redirectAction = redirectTo;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string userLogon = filterContext.HttpContext.User.Identity.Name;
if (!this.databaseRepository.UserHasPermission(userLogon))
{
string routeController = filterContext.Controller.ControllerContext.RouteData.Values["controller"];
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = routeController, action = this.redirectAction }));
}
}
}
Your controller would then look something like this:
[UserHasPermission("NoAccess")]
public ActionResult SecretArea()
{
// run all the logic
return View();
}
public ActionResult NoAccess()
{
return View();
}
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; }
}