Extend AuthorizeAttribute to detect logged in non-user (How to handle user authorization) - asp.net-mvc

Environment: ASP.NET MVC 4, Visual Studio 2012
The [Authorize] attribute verifies that the user has a valid login cookie, but it does NOT verify that the user actually exists. This would happen if a user is deleted while that user's computer still holds the persisted credentials cookie. In this scenario, a logged-in non-user is allowed to run a controller action marked with the [Authorize] attribute.
The solution would seem to be pretty simple: Extend AuthorizeAttribute and, in the AuthorizeCore routine, verify that the user exists.
Before I write this code for my own use, I'd like to know if someone knows of a ready-to-go solution to this gaping hole in the [Authorize] attribute.

You need a special authentication global action filter.
Solution to your problem is the following. You have to introduce the global action filter that will be executed before controller action is invoked. This event is named OnActionExecuting. And within this global action filter you can also handle the scenario that user have a valid auth cookie, but does not exists in persistence (DB) anymore (and you have to remove its cookie).
Here is the code example to get an idea:
public class LoadCustomPrincipalAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CustomIdentity customIdentity;
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
UserData userData = UserRepository.GetUserByName(HttpContext.Current.User.Identity.Name);
if (userData == null)
{
//TODO: Add here user missing logic,
//throw an exception, override with the custom identity with "false" -
//this boolean means that it have IsAuthenticated on false, but you
//have to override this in CustomIdentity!
//Of course - at this point you also remove the user cookie from response!
}
customIdentity = new CustomIdentity(userData, true);
}
else
{
customIdentity = new CustomIdentity(new UserData {Username = "Anonymous"}, false);
}
HttpContext.Current.User = new CustomPrincipal(customIdentity);
base.OnActionExecuting(filterContext);
}
}
Hope it helps to you!
Do not forget to register this action filter as a global one. You can do this like:
private static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LoadCustomPrincipalAttribute());
}
Just to add this. Leave alone AuthorizeAttribute. It should work as it was meant. It simply check the HttpContext.Current.User.Identity.IsAuthenticated == true condition. There are situations that you would need to overide it, but this is not the one. You really need a proper user/auth handling before even AuthorizeAttribute kicks in.

Agreed with Peter. Here is what I did for an AngularJs app. Create an attribute that checks the lockout date. Change YourAppUserManager out with the correct one.
public class LockoutPolicyAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
var now = DateTime.UtcNow;
var currentUserId = Convert.ToInt32(HttpContext.Current.User?.Identity?.GetUserId());
var user = await HttpContext.Current.GetOwinContext().GetUserManager<YourAppUserManager>().FindByIdAsync(currentUserId);
if (user?.LockedOutUntil >= now)
{
actionContext.Response = actionContext.Request.CreateErrorResponse((HttpStatusCode)423, "Account Lockout");
return;
}
}
base.OnActionExecuting(actionContext);
}
}
Then have an AngularJs intercept service for status code 423 to redirect to login page.
switch (response.status) {
case 423: //Account lockout sent by the server.
AuthService.logOut();
window.location.href = '/login';

Related

Derived from AuthorizeAttribute but User.Identity.Name is null unless using AuthorizeAttribute

So I've created a custom authorize attribute I use in a few places that is derived from an abstract base class which is derived from AuthorizeAttribute:
CustomAuthorizeAttributeBase.cs
public abstract class CustomAuthorizeAttributeBase : AuthorizeAttribute
{
public abstract string GetUsers();
public abstract string GetRoles();
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.IsChildAction)
{
return;
}
filterContext.Result =
new RedirectToRouteResult(new RouteValueDictionary
{
{"controller", "NotAuthorized"},
{"action", "Index"},
});
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (GetUsers().IndexOf(httpContext.User.Identity.Name, StringComparison.CurrentCultureIgnoreCase) >= 0 ||
GetRoles().Split(',').Any(s => httpContext.User.IsInRole(s)))
{
return true;
}
return false;
}
}
AreaLevelReadonly.cs
public class AreaLevelReadOnly : CustomAuthorizeAttributeBase
{
public override string GetUsers()
{
return ConfigurationManager.AppSettings["AreaReadonlyUsers"];
}
public override string GetRoles()
{
return ConfigurationManager.AppSettings["AreaReadonlyRoles"];
}
}
I also have some fairly simple code that gets me the currently logged in user:
UserIdentity.cs
public class UserIdentity : IUserIdentity
{
public string GetUserName()
{
return HttpContext.Current.User.Identity.Name.Split('\\')[1];
}
}
However, when I add my AreaLevelReadonly attribute to my controllers, getUserName fails and returns an exception that Name is null. I agonized over it for about an hour before putting authorize attribute on there as well, at which point it magically started working again. So, what is so different on the implementation level that my attribute deriving from authorizeattribute doesn't cause the Name to be populated.
Note: Windows authentication is on for the area, and the code works, but I don't understand why the Readonly attribute isn't enough to trigger authorization and population of the HttpContext.Current.User.Identity.Name.
Edit: Working:
[AreaLevelReadonly]
[Authorize]
public class DeleteAreaDataController : Controller {
//etc
var username = _userIdentity.GetUserName(HttpContext);
//etc
}
Exception on name:
[AreaLevelReadonly]
public class DeleteAreaDataController : Controller {
//etc
var username = _userIdentity.GetUserName(HttpContext);
//etc
}
More likely than not, you're accessing User.Identity.Name before it's populated. By including the standard Authorize attribute, as well, your code is then only running after the user has been authorized already and User.Identity.Name has been populated.
EDIT
Sorry, I misunderstood where the code attempting to call User.Identity.Name was running. Based on the belief that it was happening in your custom attribute, I was suggesting that you're trying to access it too early. However, I now see that you're calling it in your controller (although an explanation of what happens in GetUserAccount(HttpContext) would have helped.)
Anyways, your custom attribute obviously adds extra conditions on whether a user is authorized or not. When you return false, there is no user. It's not a situation where the user is "logged in" but not allowed to see the page. It's either there or it isn't. So the user is failing authorization based on your custom attribute (User.Identity.Name is null) but is authorized when you include Authorize (User.Identity.Name has a value).
Long and short, your GetUserName or GetUserAccount or whatever code needs to account for when the user has failed authorization. Or, if the user shouldn't be failing authorization, you'll need to look into why your custom attribute isn't working. Though, either way, you should still account for User.Identity.Name being null.
Your custom attribute is probably reading User.Identity.Name before you check that the user is authenticated.
In other words, in IsAuthorized(), before you read User.Identity.Name, you should be doing something like this:
if (!user.Identity.IsAuthenticated)
{
// Your custom code...
return false;
}
The reason you need this is because Windows Authentication (at least for NTLM) is a 2-step negotiation process between the client and server (see https://support.citrix.com/article/CTX221693). There will be 2 requests - the first with no name, and the second with a name. You can test this yourself - the source code for AuthorizeAttribute is provided here. Copy/paste that into your code and put a breakpoint in IsAuthorized - you will see that the breakpoint is hit twice. First time, the name is null, second time, it's set to your username.
So I think the solution is to either check user.Identity.IsAuthenticated at the start of your method, if you need to run custom code (as shown above), or alternatively if you only need to return false, simply replace the above code with base.IsAuthorized() which should do it for you.

Restrict access to a controller based on certain criteria not user authentication

What is the correct way to restrict access to a controller?
For instance, I might have "ProductReviewController", I want to be able to check that this controller is accessible in the current store and is enabled. I'm not after the code to do that but am interested in the approach to stopping the user getting to the controller should this criteria not be met. I would like the request to just carry on as if the controller was never there (so perhaps throwing a 404).
My thoughts so far:
A data annotation i.e [IsValidController]. Which Attribute class would I derive from - Authorize doesn't really seem to fit and I would associate this with user authentication. Also, I'm not sure what the correct response would be if the criteria wasn't met (but I guess this would depend on the Attribute it's deriving from). I could put this data annotation against my base controller.
Find somewhere lower down in the page life cycle and stop the user hitting the controller at all if the controller doesn't meet my criteria. i.e Create my own controller factory as depicted in point 7 here: http://blogs.msdn.com/b/varunm/archive/2013/10/03/understanding-of-mvc-page-life-cycle.aspx
What is the best approach for this?
Note: At the moment, I am leaning towards option 1 and using AuthorizeAttribute with something like the code below. I feel like I am misusing the AuthorizeAttribute though.
public class IsControllerAccessible : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!CriteriaMet())
return false;
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
controller = "Generic",
action = "404"
})
);
}
}
I think you are confused about AuthorizeAttribute. It is an Action Filter, not a Data Annotation. Data Annotations decorate model properties for validatioj, Action Filter's decorate controller actions to examine the controller's context and doing something before the action executes.
So, restricting access to a controller action is the raison d'etre of the AuthorizeAttribute, so let's use it!
With the help of the good folks of SO, I created a customer Action Filter that restricted access to actions (and even controllers) based on being part of an Access Directory group:
public class AuthorizeADAttribute : AuthorizeAttribute
{
public string Groups { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (base.AuthorizeCore(httpContext))
{
/* Return true immediately if the authorization is not
locked down to any particular AD group */
if (String.IsNullOrEmpty(Groups))
return true;
// Get the AD groups
var groups = Groups.Split(',').ToList<string>();
// Verify that the user is in the given AD group (if any)
var context = new PrincipalContext(ContextType.Domain, "YOURADCONTROLLER");
var userPrincipal = UserPrincipal.FindByIdentity(context,
IdentityType.SamAccountName,
httpContext.User.Identity.Name);
foreach (var group in groups)
{
try
{
if (userPrincipal.IsMemberOf(context, IdentityType.Name, group))
return true;
}
catch (NoMatchingPrincipalException exc)
{
var msg = String.Format("While authenticating a user, the operation failed due to the group {0} could not be found in Active Directory.", group);
System.ApplicationException e = new System.ApplicationException(msg, exc);
ErrorSignal.FromCurrentContext().Raise(e);
return false;
}
catch (Exception exc)
{
var msg = "While authenticating a user, the operation failed.";
System.ApplicationException e = new System.ApplicationException(msg, exc);
ErrorSignal.FromCurrentContext().Raise(e);
return false;
}
}
}
return false;
}
}
Note this will return a 401 Unauthorized, which makes sense, and not the 404 Not Found you indicated above.
Now, the magic in this is you can restrict access by applying it at the action level:
[AuthorizeAD(Groups = "Editor,Contributer")]
public ActionResult Create()
Or at the controller level:
[AuthorizeAD(Groups = "Admin")]
public class AdminController : Controller
Or even globally by editing FilterConfig.cs in `/App_Start':
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new Code.Filters.MVC.AuthorizeADAttribute() { Groups = "User, Editor, Contributor, Admin" });
}
Complete awesome sauce!
P.S. You mention page lifecycle in your second point. There is no such thing in MVC, at least not in the Web Forms sense you might be thinking. That's a good thing to my mind, as things are greatly simplified, and I don't have to remember a dozen or so different lifecycle events and what the heck each one of them is raised for!

Best approach to don't request same info over and over

On my controller I have it inherit a MainController and there I override the Initialize and the OnActionExecuting.
Here I see what is the URL and by that I can check what Client is it, but I learned that for every Method called, this is fired up again and again, even a simple redirectToAction will fire the Initialization of the same controller.
Is there a better technique to avoid this repetition of database call? I'm using Entity Framework, so it will take no time to call the DB as it has the result in cache already, but ... just to know if there is a better technique now in MVC3 rather that host the variables in a Session Variable
sample code
public class MyController : MainController
{
public ActionResult Index()
{
return View();
}
}
public class MainController : Controller
{
public OS_Clients currentClient { get; set; }
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// get URL Info
string url = requestContext.HttpContext.Request.Url.AbsoluteUri;
string action = requestContext.RouteData.GetRequiredString("action");
string controller = requestContext.RouteData.GetRequiredString("controller");
object _clientUrl = requestContext.RouteData.Values["cliurl"];
if (_clientUrl != null && _clientUrl.ToString() != "none")
{
// Fill up variables
this.currrentClient = db.FindClientById(_clientUrl.ToString());
}
base.Initialize(requestContext);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// based on client and other variables, redirect to Disable or Login Actions
// ... more code here like:
// filterContext.Result = RedirectToAction("Login", "My");
base.OnActionExecuting(filterContext);
}
}
is it still best to do as:
public OS_Clients currentClient {
get {
OS_Clients _currentClient = null;
if (Session["CurrentClient"] != null)
_currentClient = (OS_Clients)Session["CurrentClient"];
return _currentClient;
}
set {
Session["CurrentClient"] = value;
}
}
It seems that you dealing with application security in that case I would suggest to create Authorization filter, which comes much early into the action. You can put your permission checking code over there and the framework will automatically redirect the user to login page if the permission does not meet AuthorizeCore.
Next, if the user has permission you can use the HttpContext.Items as a request level cache. And then you can create another ActionFilter and in action executing or you can use the base controller to get the user from the Httpcontext.items and assign it to controller property.
If you are using asp.net mvc 3 then you can use the GlobalFilters to register the above mentioned filters instead of decorating each controller.
Hope that helps.
In your base controller, you need to cache the result of the first call in a Session variable.
This makes sure the back-end (DB) is not called unnecessarily, and that the data is bound to the user's Session instead of shared across users, as would be the case with the Application Cache.

Two step authentication in MVC?

We have an MVC app which has a custom forms authentication view/controller. The controller will verify things and then do a FormsAuthentication.RedirectFromLoginPage call.
At this point in the Global.asax we'll receive a Application_OnAuthenticateRequest call from where we'll get their Context.User information and make another call to gather information relevant to this account which we then store in their Context.User & System.Threading.Thread.CurrentPrincipal. We also do a little caching of this information since in our system retrieving what we need is expensive which leads to cache invalidation & re-retrieval of this information.
It seems a bit odd at this point that we've got these separated into separate calls. I'm almost wondering if the Login controller shouldn't be gathering the details as part of its authentication check and storing them. Then the Application_OnAuthenticateRequest can only worry about if the cache needs to be invalidated and the users details re-retrieved.
Or maybe there is some other way of handling this I don't even know about..?
You can do what you want in MVC by leveraging RedirectToRouteResult and a custom cache updating ActionFilter. This is called the PRG (Post-Redirect-Get) pattern. You are actually already doing this, but it gets a little confused, because what you are doing is a cross between the classic ASP.NET way of doing things and the MVC way of doing things. There's nothing wrong with your initial approach (provided it is working correctly), but to do the same sort of thing and have more control and understanding of how it works in the scheme of things you could do something like:
public class AuthenticationController :Controller
{
[HttpPost]
public RedirectToRouteResult Login(string username, string password)
{
//authenticate user
//store authentication info in TempData like
bool authenticated = true|false; // do your testing
if(authenticated)
{
TempData["MustUpdateCache"] = true | false;
return RedirectToAction("LoginSuccess", new{userId = membershipUser.UserId});
}
else
{
TempData["MustUpdateCache"] = true | false;
return RedirectToAction("Login");
}
}
[HttpGet, UpdateCache]
public ActionResult LoginSuccess(Guid userId, string url)
{
HttpContext.User = LoadUser(userId);
return View();
}
[HttpGet, UpdateCache]
public ViewResult Login()
{
return View();
}
}
public class UpdateCacheAttribute:ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var tempData = filterContext.Controller.TempData;
if (tempData.ContainsKey("MustUpdateCache") && (bool)tempData["MustUpdateCache"])
{
UpdateCache(filterContext);
}
}
void UpdateCache(ControllerContext controllerContext)
{
//update your cache here
}
}

Custom Authorization Attribute

I am implementing a CustomAuthorizeAttribute. I need to get the name of the action being executed. How can i get the name of current action name getting executed in the AuthorizeCore function which i am overriding ?
If you're using cache (or have plans to), then overriding AuthorizeCore, like Darin Dimitrov shows in this answer is a much safer bet:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var routeData = httpContext.Request.RequestContext.RouteData;
var controller = routeData.GetRequiredString("controller");
var action = routeData.GetRequiredString("action");
...
}
The reason for this is documented in the MVC source code itself:
AuthorizeAttribute.cs (lines 72-101)
public virtual void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
if (OutputCacheAttribute.IsChildActionCacheActive(filterContext)) {
// If a child action cache block is active, we need to fail immediately, even if authorization
// would have succeeded. The reason is that there's no way to hook a callback to rerun
// authorization before the fragment is served from the cache, so we can't guarantee that this
// filter will be re-run on subsequent requests.
throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
}
if (AuthorizeCore(filterContext.HttpContext)) {
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else {
HandleUnauthorizedRequest(filterContext);
}
}
Even if you didn't plan on using cache, those two magic strings seem a small price to pay for the peace of mind you get in return (and the potential headaches you save yourself.) If you still want to override OnAuthorization instead, you should at least make sure the request isn't cached. See this post by Levi for more context.
You can get the Action Name like this:
public class CustomAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
filterContext.ActionDescriptor.ActionName;
}
}
EDIT:
If you want to inherit from the AuthorizationAttribute you'll need to override the OnAuthorization method.
public class CustomAuthAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
}

Resources