Role-Based default starting View - asp.net-mvc

I've been able to find several examples on role based authentication, but this isn't an authentication issue. I have three user types, one of which I want to have a different default starting page. Route-config is initialized before the the user information is available.
In a nut shell: If Role A or Role B start below
Controller: Home
Action: Index
else:
Controller: Other
Action: OtherIndex
Where and how should this be implemented?
EDIT
This should only occur the first time the site is accessed, the other users can go to Home/Index, just not by default.
EDIT
Using Brad's Suggestion I created a redirect attribute with his redirection logic, and applied it to Index. I then created another action for the page. This way if I do need to allow access to the HomeIndex I can specifically assign it with Home/HomeIndex, and anything that uses the default routing can hit the Home/Index
[RedirectUserFilter]
public ActionResult Index()
{
return HomeIndex();
}
For those who need it - here is the Attribute
public class RedirectUserFilterAttribute: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
if (!HttpContext.Current.User.IsInRole("Role A")
&& !HttpContext.Current.User.IsInRole("Role B"))
{
actionContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "Controller", "OtherController" },
{ "Action", "OtherAction" } });
}
}
}

public class HomeController : Controller
{
public ActionResult Index()
{
if (
!User.IsInRole("Something") &&
!User.IsInRole("Role B")
) return RedirectToAction("OtherIndex");
// ...
}
}
Same goes for OtherController.
Alternatively you could create your own ActionFilter that looks for a conventionally-named action (like IndexRolename over Index)

Related

Implementing 'ReAuthentication' attribute in ASP.NET MVC 4?

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!

How to restrict access to certain actions in controller in ASP.net MVC

I am new to ASP.net MVC and created my first web application using it. In my application I am using database authentication. I have created Login action in controller which checks entered username and password exist in DB or not, If it exist then put required values in Session and redirect user to pages as per his rights else redirect user to login page. Like this
public ActionResult Login()
{
if(uservalid)
{
//set session values and redirect to dashboard
}
else
{
//redirect to login
}
}
In my application there are some functionality that can only be accessed when user is logged-in. I want to check whether user is logged-in or not before user try to access these functionality and if he is not logged-in or not have rights then redirect to login page or show some error message.
public ActionResult SomeAction()
{
//Available only when user is logged-in
}
So how do I check whether user is logged-in or not and give access to action. I read about Authorize attribute but don't know how to use it as I am using database authentication.
If you are using FormsAuthentication you don't need to use ASP.NET session to track the currently authenticated user.
I read about Authorize attribute but don't know how to use it as I am
using database authentication.
Assuming you went with FormsAuthentication, once you have validated the credentials of the user you should set a forms authentication cookie:
public ActionResult Login()
{
if(uservalid)
{
FormsAuthentication.SetAuthCookie("username", false);
return RedirectToAction("SomeProtectedAction");
}
else
{
//redirect to login
}
}
and then:
[Authorize]
public ActionResult SomeAction()
{
string currentlyLoggedInUser = User.Identity.Name;
}
By the way if you create a new ASP.NET MVC application using the internet template in Visual Studio you might take a look at the AccountController which is responsible for authenticating users and setting forms authentication cookies. Of course you could throw all the Entity Framework crap out of it and implement your own credentials validation against your own database tables.
I apply [Authorize] as well as my own customattribute for restricting the action based on permission. The code is below
[Authorize]
[FeatureAuthentication(AllowFeature=FeatureConst.ShowDashboard)]
public ActionResult Index()
{
}
Filter code
public class FeatureAuthenticationAttribute : FilterAttribute, IAuthorizationFilter
{
public FeatureConst AllowFeature { get; set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
//var featureConst = (FeatureConst)filterContext.RouteData.Values["AllowFeature"];
var filterAttribute = filterContext.ActionDescriptor.GetFilterAttributes(true)
.Where(a => a.GetType() == typeof(FeatureAuthenticationAttribute));
if (filterAttribute != null)
{
foreach (FeatureAuthenticationAttribute attr in filterAttribute)
{
AllowFeature = attr.AllowFeature;
}
User currentLoggedInUser = (User)filterContext.HttpContext.Session["CurrentUser"];
bool allowed = ACLAccessHelper.IsAccessible(AllowFeature.ToString(), currentLoggedInUser);
// do your logic...
if (!allowed)
{
string unAuthorizedUrl = new UrlHelper(filterContext.RequestContext).RouteUrl(new { controller = "home", action = "UnAuthorized" });
filterContext.HttpContext.Response.Redirect(unAuthorizedUrl);
}
}
}
}
you should create a basecontroller and inherit other controlers from base controller and then check whether the session is null or not to authenticate users.
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Session["User"]== null)
{
filterContext.HttpContext.Response.Redirect("/somepage");
}
}
public class SomeController : BaseController
{
}
There are multiple ways of doing it but the preferred way would be to use the Annotation. Here is a post for it
How to get custom annotation attributes for a controller action in ASP.NET MVC 4?
If you are getting started I would suggest to follow the tutorial on
http://www.asp.net/mvc

How do I redirect user to another Controller Action from an ASP.MVC 3 action filter?

In building a custom ASP.MVC 3 Action Filter, how should I redirect the user to another action if my test fails? I would like to pass along the original Action so that I can redirect back to the original page after the user enters the missing preference.
In controller:
[FooRequired]
public ActionResult Index()
{
// do something that requires foo
}
in a custom filter class:
// 1. Do I need to inherit ActionFilterAttribute or implement IActionFilter?
public class FooRequired : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (TestForFoo() == false)
{
// 2. How do I get the current called action?
// 3. How do I redirect to a different action,
// and pass along current action so that I can
// redirect back here afterwards?
}
// 4. Do I need to call this? (I saw this part in an example)
base.OnActionExecuting(filterContext);
}
}
I'm looking for a simple ASP.MVC 3 filter example. So far my searches have lead to Ruby on Rails examples or ASP.MVC filter examples much more complicated than I need. I apologize if this has been asked before.
Here's a small code sample using one of my own Redirect filters:
public class PrelaunchModeAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//If we're not actively directing traffic to the site...
if (ConfigurationManager.AppSettings["PrelaunchMode"].Equals("true"))
{
var routeDictionary = new RouteValueDictionary {{"action", "Index"}, {"controller", "ComingSoon"}};
filterContext.Result = new RedirectToRouteResult(routeDictionary);
}
}
}
If you want to intercept the route, you can get that from the ActionExecutingContext.RouteData member.
With that RouteData member, you can get the original route:
var currentRoute = filterContext.RouteData.Route;
Etc... Does that help answer your question?
You can set the filterContext.Result to a RedirectToRouteResult:
filterContext.Result = new RedirectToRouteResult(...);

ASP .NET MVC Securing a Controller/Action

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();
}

Overriding controller AuthorizeAttribute for just one action

I have a controller decorated with an AuthorizeAttribute. The controller contains several actions that all require authentication apart from one action that requires some custom authentication provided by CustomAuthorizeAttribute.
My question is once I've added [Authorize] at the controller level can I override it (or remove it) with [CustomAuthorize] on just one action? Or do I have to remove [Authorize] from the controller level and add it individually to every other action?
I'm asking purely for convenience because I'm lazy and don't want to decorate every action with the AuthorizeAttribute.
[Authorize]
public class MyController : Controller {
//requires authentication
public ViewResult Admin() {
return View();
}
//... a lot more actions requiring authentication
//requires custom authentication
[CustomAuthorize] //never invoked as already failed at controller level
public ViewResult Home() {
return View();
}
}
In MVC 5 you can override the authorization for any action using the new attribute OverrideAuthorization. Basically, you add it to an action that has a different authorization configuration than the one defined in the controller.
You do it like this:
[OverrideAuthorization]
[Authorize(Roles = "Employee")]
public ActionResult List() { ... }
More information at http://www.c-sharpcorner.com/UploadFile/ff2f08/filter-overrides-in-Asp-Net-mvc-5/
In ASP.NET Core 2.1 there's no OverrideAuthorization attribute and the only thing you can do is make an action anonymous, even if the controller is not. More information at https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-2.1
One option is to do it this way:
[Authorize(Roles = "Admin,Employee")] // admin or employee
public class XController : Controller
{
[Authorize(Roles = "Admin")] // only admin
public ActionResult ActionX() { ... }
[AllowAnonymous] // anyone
public ActionResult ActionX() { ... }
}
You can change the Order in which the attributes run (using the Order property), but I believe that in this case they will still both run unless one generates a result with immediate effect. The key is to have the least restrictive attribute applied at the highest level (class) and get more restrictive for the methods. If you wanted the Home action to be publicly available, for instance, you would need to remove the Authorize attribute from the class, and apply it to each of the other methods.
If the action has the same level of permissiveness, but has a different result, changing the order may be sufficient. For example, you would normally redirect to the Logon action, but for Home you want to redirect to the About action. In this, case give the class attribute Order=2 and the Home action attribute Order=1.
After way too much time, I came up with a solution. You need to decorate your controller with a custom AuthorizeAttribute.
public class OverridableAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var action = filterContext.ActionDescriptor;
if(action.IsDefined(typeof(IgnoreAuthorization), true)) return;
var controller = action.ControllerDescriptor;
if(controller.IsDefined(typeof(IgnoreAuthorization), true)) return;
base.OnAuthorization(filterContext);
}
}
Which can be paired with AllowAnonymous on an Action
[AllowAnonymous]
All you need to override the [Authorize] from the controller, for a specific action is to add
[AllowAnonymous]
to the action you want to not be authorized (then add your custom attribute as required).
See the comments / intellisense :
Represents an attribute that marks controllers and actions to skip the
System.Web.Mvc.AuthorizeAttribute during authorization.
Full Example
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Website
{
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (true)//Perform own authorization logic
return; //simply return if request is authorized
context.Result = new UnauthorizedResult();
return; //this is not authorized
}
}
[Authorize]
public class WebsiteController : Controller
{
[HttpGet]
[AllowAnonymous]//When this is added our Custom Attribute is hit, without it our attribute is not used as request already gets 401 from controller's Authorize
[CustomAuthorize]
public IActionResult Index()
{
return View(new ViewModel());
}
}
Note
This approach will not work if you want to use the standard [Authorize] attribute on your action, with a custom policy e.g.
[Authorize]
public class WebsiteController : Controller
{
[HttpGet]
[AllowAnonymous]
[Authorize("CustomPolicyName")] //Will not be run
public IActionResult Index()
{
return View(new ViewModel());
}
}
services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>
policy.RequireAssertion(context =>
false //Custom logic here
));
});
...but if like the OP you want a Custom Attribute then you are good to go with my solution.
Override for all controllers when handling prototype and production environment.
So there is no need to remove the authorize of each controller.
app.UseEndpoints(endpoint =>
{
endpoint.MapControllers().WithMetadata(new AllowAnonymousAttribute());
});

Resources