Authorize Attribute working unexpectively - c#

I have extended the Authorize attribute to include roles which comes from a cookie. Debugging gives good result, it returns true or false accordingly. However if I first log in with "Admin" Role and then try to go to a controller that requires a User role, the Authorize returns false but still the controller allows access.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) throw new ArgumentNullException("httpContext");
if (httpContext.User != null)
{
if (httpContext.User.Identity.IsAuthenticated)
{
if (httpContext.User.Identity is FormsIdentity)
{
FormsIdentity id = httpContext.User.Identity as FormsIdentity;
FormsAuthenticationTicket ticket = id.Ticket;
string role = ticket.UserData;
if (RequiredRole.Contains(role)) return true;
}
}
else
return false;
}
return false;
}
Requiredrole is a property of the class.
[CustomAuthorize(RequiredRole = "Admin", LoginPage = "Club")]
public class UsuarioAdminController : Controller
{
above code for a controller that requires admin role.
[CustomAuthorize(RequiredRole = "User", LoginPage = "Club")]
public class HotelController : Controller
{
above code for a controller with User role.
Can someone see why if Authorize returns false it allows access? Thanks
The AuthorizeCore Attribute behave as expected, it returns true or false; however the controller allows access when the AuthorizeCore method returns false.
Yes, there is more code, but I dont think it makes a differnce..here it is.
public class CustomAuthorizeAttribute: AuthorizeAttribute
{
public string RequiredRole;
public string LoginPage;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) throw new ArgumentNullException("httpContext");
if (httpContext.User != null)
{
if (httpContext.User.Identity.IsAuthenticated)
{
if (httpContext.User.Identity is FormsIdentity)
{
FormsIdentity id = httpContext.User.Identity as FormsIdentity;
FormsAuthenticationTicket ticket = id.Ticket;
string role = ticket.UserData;
if (RequiredRole.Contains(role)) return true;
}
}
else
return false;
}
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
var routeValues = new RouteValueDictionary();
if (LoginPage == "Club")
{
routeValues["action"] = "Index";
routeValues["controller"] = LoginPage;
routeValues["ReturnUrl"] = filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectToRouteResult(routeValues);
}
else {
routeValues["area"] = "mobile";
routeValues["action"] = "login";
routeValues["controller"] = LoginPage;
routeValues["ReturnUrl"] = filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectToRouteResult(routeValues);
}
}
}
}

I found the answer to this particular situation and I am sharing my findings. When a request was properly unauthorized it was handled by the
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
method. Inside that method I was checking first if the user is not authenticated. For the first request it works properly sending the user to the appropriate log in page, But now if you try to navigate to another page that requires a different role, because you are already authenticated it wont go inside the redirect and will allow access to the control. So by removing the !IsAuthenticated if at the beginning, now all unauthorized request is properly sent to the correct login page...

Related

Why is Session null when using Output Caching and is there a solution for it?

I have created a custom Authorize attribute to authorize users trough a remote web API. After authorization I receive a object with token that is valid for some specific time and is used to access further information and I also get some basic user data like name, surname, role, etc ... which I store in Session.
Everything worked just fine but when I tried using Output Caching the Session I'm accessing in my Authorization Core method is null and application crashes there.
How to solve this problem or perhaps an alternative approach avoiding this as last resort?
Authorize attribute
public class AuthorizeUser : AuthorizeAttribute
{
private class Http401Result : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// Set the response code to 401.
context.HttpContext.Response.StatusCode = 401;
context.HttpContext.Response.Write("Session expired, please log in again.");
context.HttpContext.Response.End();
}
}
private readonly string[] users;
public AuthorizeUser(params string[] usrs)
{
this.users= usrs;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool auth = false;
var loggedInUser= httpContext.Session["LoggedInUser"] as User;
if (loggedInUser != null)
auth = true;
return auth;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
filterContext.Result = new Http401Result();
else
filterContext.Result = new RedirectToRouteResult
(
new RouteValueDictionary
(
new
{
controller = "Account",
action = "Login",
}
)
);
}
}
Controller Setup
[AuthorizeUser]
public class SomeController : Controller
{
[HttpPost]
[OutputCache(VaryByParam ="Year", Duration = 3600)]
public async Task<JsonResult> SomeAction(int Year){ ... }
}

How to solve windows security window prompt for Authorization failed in mvc

My Question:
When user doesn't have Manager Role and Admin Role, I have to redirect to error page/some popup message. But when I checked if authorize "false" continuously windows security password windows its showing. When I entered user name and password again its showing windows security password.
Every action method I have to check and I need to show the message or error page. how to solve this issues?
Controller Code:
[AuthorizeUser("Manager","Admin")]
public ActionResult Contact()
{
return View();
}
C# Code:
public AuthorizeUserAttribute(params int[] roles)
{
allowedroles = roles;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool authorize = false;
var getList = _objService.GetUserRoleDetail(CommonStaticHelper.getLoggedUser());
foreach (var role in allowedroles)
{
if (getList.Exists(m => m.RoleId == role))
{
return authorize = true; /* return true if Entity has current user(active) with specific role */
}
}
return authorize;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new HttpUnauthorizedResult();
}
/// Try this :
///Create an action :
public ActionResult Unauthorized()
{
return View();
}
//// now write below code for authorization
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
//redirect to the Unauthenticated page
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "Error", action = "Unauthorized"
}));
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// The user is not authenticated
return false;
}
else{
var getList =
_objService.GetUserRoleDetail(CommonStaticHelper.getLoggedUser());
foreach (var role in allowedroles)
{
if (getList.Exists(m => m.RoleId == role))
{
return authorize = true; /* return true if Entity has current
user(active) with specific role */
}
}
return authorize = false;
}
create your own Filter something like
public class AuthorityAttribute : AuthorizeAttribute
{
private readonly string[] allowedroles;
public AuthorityAttribute(params string[] roles)
{
this.allowedroles = roles;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
foreach (var role in allowedroles)
{
if (PortalWebSessionManager.ActivePortalSettings.ActiveRoles != null)
{
foreach (IDynamics.IDynamicsPortal.DataComponent.Roles currentRole in PortalWebSessionManager.ActivePortalSettings.ActiveRoles)
{
if (currentRole.RoleName == role)
{
return true;
}
}
}
}
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
and call that filter

Custom ASP.NET MVC Forms Authentication

I want to do custom authentication because we have many controllers and it makes sense to create global filter that applies for all controllers and their actions with exception of login page.
In Global.asax.cs I added next global filter:
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e) // Code that runs on application startup
{
... // only showing important part
GlobalFilters.Filters.Add(new Filters.AuthenticationUserActionFilter());
...
}
File AuthenticationUserActionFilter.cs:
public class AuthorizeUserActionFilter : System.Web.Mvc.Filters.IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousActionFilter), inherit: true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousActionFilter), inherit: true);
if (skipAuthorization) // anonymous filter attribute in front of controller or controller method
return;
// does this always read value from ASPXAUTH cookie ?
bool userAuthenticated = filterContext.HttpContext.User.Identity.IsAuthenticated;
if (!userAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary() { { "controller", "Account" }, { "action", "Login" } });
return;
}
if( HttpContext.Current.User as Contracts.IUser == null )
{
// check if IUser is stored in session otherwise retrieve from db
// System.Web.HttpContext.Current.User is reseted on every request.
// Is it ok to set it from Session on every request? Is there any other better approach?
if (HttpContext.Current.Session["User"] != null && HttpContext.Current.Session["User"] as Contracts.IUser != null)
{
HttpContext.Current.User = HttpContext.Current.Session["User"] as Contracts.IUser;
}
else
{
var service = new LoginService();
Contracts.ISer user = service.GetUser(filterContext.HttpContext.User.Identity.Name);
HttpContext.Current.Session["User"] = user;
HttpContext.Current.User = user;
}
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext) {}
}
My login code is like this (in AccountController.cs):
[Filters.AllowAnonymousActionFilter]
[HttpPost]
public JsonResult Login(string username, string password, bool rememberMe = false)
{
LoginService service = new LoginService();
Contracts.IUser user = service .Login(username, password);
System.Web.HttpContext.Current.Session["User"] = value;
System.Web.HttpContext.Current.User = value;
// set cookie i.e. ASPX_AUTH, if remember me, make cookie persistent, even if user closed browser
if (System.Web.Security.FormsAuthentication.IsEnabled)
System.Web.Security.FormsAuthentication.SetAuthCookie(username, rememberMe);
return new SuccessResponseMessage().AsJsonNetResult();
}
Contracts.IUser interface:
public interface IUser : IPrincipal
{
Contracts.IUserInfo UserInfo { get; }
Contracts.ICultureInfo UserCulture { get; }
}
My question is this:
System.Web.HttpContext.Current.User is reseted on every request. Is it ok to set HttpContext.Current.User with Session value on every request? Is there any other better approach? What is best practise? Also Microsoft seems to have multiple ways of dealing with this problem (googled a lot of articles on this, also on stackoverflow Custom Authorization in Asp.net WebApi - what a mess?). There is a lot of confusion about this, although they developed a new Authorization in asp.net core.
One possible approach is to serialize the user as part of the UserData portion of the ASPXAUTH cookie. This way you don't need to fetch it from the database on each request and you don't need to use Sessions (because if you use sessions in a web-farm you will have to persist this session somewhere like in a database, so you will be round-tripping to the db anyway):
[Filters.AllowAnonymousActionFilter]
[HttpPost]
public JsonResult Login(string username, string password, bool rememberMe = false)
{
LoginService service = new LoginService();
Contracts.IUser user = service.Login(username, password);
string userData = Serialize(user); // Up to you to write this Serialize method
var ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddHours(24), rememberMe, userData);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket));
return new SuccessResponseMessage().AsJsonNetResult();
}
And then in your custom authorization filter you could decrypt the ticket and authenticate the user:
public void OnAuthentication(AuthenticationContext filterContext)
{
... your stuff about the AllowAnonymousActionFilter comes here
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null)
{
// Unauthorized
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary() { { "controller", "Account" }, { "action", "Login" } });
return;
}
// Get the forms authentication ticket.
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
Contracts.ISer user = Deserialize(authTicket.UserData); // Up to you to write this Deserialize method -> it should be the reverse of what you did in your Login action
filterContext.HttpContext.User = user;
}

Login Dialog Appears When User is Not Authorized

In an attempt to implement security in my web app, I created an attribute that derives from AuthorizeAttribute.
public class FunctionalityAttribute : AuthorizeAttribute
{
public string FunctionalityName { get; set; }
protected override bool IsAuthorized(HttpActionContext actionContext)
{
string adGroup = WebConfigurationManager.AppSettings[FunctionalityName];
if (actionContext.RequestContext.Principal.IsInRole(adGroup)) { return true; }
return false; // This causes a login dialog to appear. I don't want that.
}
}
And here is how it's used in my Web API method:
[Functionality(FunctionalityName = "GetApps")]
public IEnumerable<ApplicationDtoSlim> Get()
{
using (var prestoWcf = new PrestoWcf<IApplicationService>())
{
return prestoWcf.Service.GetAllApplicationsSlim().OrderBy(x => x.Name);
}
}
It actually works. But the issue is what happens when I'm not authorized:
I don't want that dialog to come up. I'm already signed in. I want to let the user know that they're not authorized. How do I make it so that login dialog doesn't come up?
You need to also override HandleUnauthorizedRequest
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
System.Web.Routing.RouteValueDictionary rd = null;
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
//Redirect to Not Authorized
rd = new System.Web.Routing.RouteValueDictionary(new { action = "NotAuthorized", controller = "Error", area = "" });
}
else
{
//Redirect to Login
rd = new System.Web.Routing.RouteValueDictionary(new { action = "Login", controller = "Account", area = "" });
//See if we need to include a ReturnUrl
if (!string.IsNullOrEmpty(filterContext.HttpContext.Request.RawUrl) && filterContext.HttpContext.Request.RawUrl != "/")
rd.Add("ReturnUrl", filterContext.HttpContext.Request.RawUrl);
}
//Set context result
filterContext.Result = new RedirectToRouteResult(rd);
}
In HandleUnauthorizedRequest, use HttpStatusCode Forbidden because Unauthorized causes a login prompt to display. Here is the entire attribute class.
public class FunctionalityAttribute : AuthorizeAttribute
{
public string FunctionalityName { get; set; }
protected override bool IsAuthorized(HttpActionContext actionContext)
{
string adGroup = WebConfigurationManager.AppSettings[FunctionalityName];
if (actionContext.RequestContext.Principal.IsInRole(adGroup)) { return true; }
return false;
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
// Authenticated, but not authorized.
if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
{
// Use Forbidden because Unauthorized causes a login prompt to display.
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
And this is how I'm handling it in my angular repository:
$http.get('/PrestoWeb/api/apps/')
.then(function (result) {
// do success stuff
}, function (response) {
console.log(response);
if (response.status == 403) {
$rootScope.setUserMessage("Unauthorized");
callbackFunction(null);
}
});

Log Out a User in MVC 5 Using a Custom ActionFilterAttribute

I have a custom ActionFilterAttribute that makes sure a value in the Session matches a value in the database. If the values don't match, it redirects the user to the Login action on the AccountController.
public class CheckSessionAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), false).Any())
{
// If the action allows Anonymous users, no need to check the session
return;
}
var session = filterContext.RequestContext.HttpContext.Session;
var userName = filterContext.RequestContext.HttpContext.User.Identity.Name;
var userStore = new ApplicationUserStore(new IdentityDb());
var userManager = new ApplicationUserManager(userStore);
var user = userManager.FindByNameAsync(userName).Result;
if (userName == null || user == null || session == null || session["ActiveSessionId"] == null ||
session["ActiveSessionId"].ToString() != user.ActiveSessionId.ToString())
{
session.RemoveAll();
session.Clear();
session.Abandon();
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new
{
action = "Login",
controller = "Account"
}
));
}
base.OnActionExecuting(filterContext);
}
}
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
SignOutAndKillSession();
ViewBag.ReturnUrl = returnUrl;
return View();
}
private void SignOutAndKillSession()
{
AuthenticationManager.SignOut();
Session.RemoveAll();
Session.Clear();
Session.Abandon();
}
}
When I try to login again after being redirected to the Login action, I get the following exception:
The provided anti-forgery token was meant for a different claims-based user than the current user
I set a breakpoint inside the Login action and can see that User.Identity.Name is still set to the user that is being logged out, before AND after the call SignOutAndKillSession(). I believe this is what's causing an incorrect AntiForgeryToken to be generated when the page renders.
Can someone help me find out how to clear the User Principal when logging out a user?
Thanks
For anyone that runs into this issue, I solved it by expiring the cookies created by MVC 5 inside the CheckSessionAttribute. I also changed the attribute from an ActionFilterAttribute to an IAuthorizationFilter Attribute
public class CheckSessionAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowAnonymousAttribute), false).Any())
{
// If the action allows Anonymous users, no need to check the session
return;
}
var session = filterContext.RequestContext.HttpContext.Session;
var userName = filterContext.RequestContext.HttpContext.User.Identity.Name;
var userStore = new ApplicationUserStore(new IdentityDb());
var userManager = new ApplicationUserManager(userStore);
var user = userManager.FindByNameAsync(userName).Result;
if (userName == null || user == null || session == null || session["ActiveSessionId"] == null ||
session["ActiveSessionId"].ToString() != user.ActiveSessionId.ToString())
{
session.RemoveAll();
session.Clear();
session.Abandon();
ExpireCookie("ASP.NET_SessionId", filterContext);
ExpireCookie("__RequestVerificationToken", filterContext);
ExpireCookie(".AspNet.ApplicationCookie", filterContext);
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new
{
action = "Login",
controller = "Account"
}
));
}
return;
}
private void ExpireCookie(string name, AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.Cookies[name] != null)
{
filterContext.RequestContext.HttpContext.Response.Cookies[name].Value = string.Empty;
filterContext.RequestContext.HttpContext.Response.Cookies[name].Expires = DateTime.Now.AddMonths(-20);
}
}
}

Categories

Resources