My Custom Authorize Attribute Always Redirects to Un authroized page - asp.net-mvc

I am writing custom authorize attribute for one requirement.
As per the requirement, I need to pass all the allowed roles for that particular action method like below.
[MyAuthorize("Admin,Reviewer")]
public ActionResult GetFXSelldownSummaryData()
{
var model = (new FXSelldownSummaryBLL()).GetFXSelldownSummaryData();
return View(model);
}
When the user logs in, the logged in user role should be compared against all the allowed roles (in the above code, all the allowed roles are Admin, and Reviewer). If the role matches, the user can see the view, otherwise the page should be navigated to Un authorized page.
I have wrriten the custom attribute like below, everything is working fine but I am ending up with Unauthorized access page for all the requests.
Can anyone please help to identify and solve the problem!
namespace MyRequirement
{
public class MyAuthorizeAttribute : AuthorizeAttribute
{
readonly string allowedRoles;
public MyAuthorizeAttribute(string allowedRoles)
{
this.allowedRoles = allowedRoles;
}
public System.Collections.Generic.List<string> AllowedRoles
{
get
{
return this.allowedRoles.Split(',').ToList();
}
}
private bool AuthorizeRole(AuthorizationContext filterContext)
{
var context = filterContext.RequestContext.HttpContext;
PnLUserDetails userDetails = System.Web.HttpContext.Current.Session["PnLUserDetails"] as PnLUserDetails;
string loggedInUserRole = userDetails.Role;
if (AllowedRoles.Contains(loggedInUserRole))
return true;
return false;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext == null)
throw new ArgumentException("filterContext");
bool authStatus = AuthorizeRole(filterContext);
if(!authStatus)
{
filterContext.Result = new HttpUnauthorizedResult();
return;
}
}
}
}

Remove the call to
base.OnAuthorization(filterContext);
Change the code like this
public override void OnAuthorization(AuthorizationContext filterContext)
{
// This line is not needed, you are handling the authorization
// This is the line that will give you the unauthorized access by default
// base.OnAuthorization(filterContext);
if (filterContext == null)
throw new ArgumentException("filterContext");
bool authStatus = AuthorizeRole(filterContext);
if(!authStatus)
{
filterContext.Result = new HttpUnauthorizedResult();
return;
}
}

Related

Trigger authorization validation manually

I've a custom AuthorizeAttribute in my website. It has some logic about the Result created for unathorized requests.
In some cases, I want to trigger its validation manually*. I don't know if its possible. As I haven't found how to do that, I thought that I could extract the logic to get the Result to a diferrent method, and call it when I want. But then I don't know how to execute the ActionResult (outside de controllers).
How can I do to manually execute authorize validation? If not possible, how can I do to execute an ActionResult outside a controller?
*I need to trigger it manually because some request may pass the validation (because the session is created) and then, when accessing my services, found that the session was closed by someone else. I wouldn't like to add a call to the services in OnAuthorization to reduce services calls.
I'm not sure if its the best, but I've found a way to get it working (still listening for better answers).
When I call the services and notice that the work session has expired, all I do is removing the active user in the web session.
My custom authorize attribute also implements IResultFilter and IExceptionFilter.
In both OnResultExecuted and OnException I validate the active user once more. If the session was removed, then apply the same ActionResult that I would apply in OnAuthorization.
Here is the final class:
public class CustomAuthorizeAttribute : AuthorizeAttribute, IResultFilter, IExceptionFilter
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
filterContext.Result = result;
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
filterContext.Result = result;
}
public void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public void OnException(ExceptionContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
{
filterContext.Result = result;
filterContext.ExceptionHandled = true;
}
}
public static ActionResult Validate(HttpContextBase httpContext)
{
if (UserActiveInSession)
return null;
// Different rules to build an ActionResult for this specific case.
}
}
I did not get Diego answer's, But Just simply answering the title, I got it to work like that, You can use it as attribute on controllers actions and also trigger it manually at any place in C# or in Razor views.
namespace SomeNameSpace
{
public class CustomAuthorizeAttributeMVC : AuthorizeAttribute
{
private readonly string[] rolesParams;
public CustomAuthorizeAttributeMVC(params string[] roles)
{
this.rolesParams = roles;
}
public bool IsAuthorized { get {
//Do your authorization logic here and return true if the current user has permission/role for the passed "rolesParams"
string[] allowedRoles = new string[] {"role 1", "role 2", "role 3"};
return allowedRoles.Intersect(rolesParams).Any(); //for the example
}
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return this.IsAuthorized;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//...
}
}
public class AuthorizeHelper
{
public static bool HasPermission(params string[] roles)
{
return new CustomAuthorizeAttributeMVC(roles).IsAuthorized;
}
}
}
Usage example:
[CustomAuthorizeAttributeMVC("role 2")]
public ActionResult SomeAction()
{
return Content("Authorized !");
}
public ActionResult SomeOtherAction()
{
if(AuthorizeHelper.HasPermission("role 2"))
{
return Content("Authorized !");
}
return Content("401 Not Authorized !");
}
And as said, it can be used in Razor views by calling it normally
#if(AuthorizeHelper.HasPermission("role 2")) {
//...
}
Thanks

Reusable way to allow an account to be used by a single person at a time

I made a functionality that prevents multiple-login for one username at the same time and I call it in Actions like this:
int userId = (int)WebSecurity.CurrentUserId;
if ((this.Session.SessionID != dba.getSessionId(userId)) || dba.getSessionId(userId) == null)
{
WebSecurity.Logout();
return RedirectToAction("Index", "Home");
}
So the point is that every time user logins I save his sessionID into database field. So if someone with same username logins over someone already logged in with same username it overwrites that database field with this new session. If sessionID in DB is not the same as current session ID of logined user it log him out.
Is there a possibility to put this part of code in 1 place or do I have to put it in every single Action in my application?
I tried in Global.asax:
void Application_BeginRequest(object sender, EventArgs e)
{
if (Session["ID"] != null)
{
int userId = Convert.ToInt32(Session["ID"]);
if ((this.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
{
WebSecurity.Logout();
}
}
}
But I can't use Session here nor WebSecurity class if I try like this:
void Application_BeginRequest(object sender, EventArgs e)
{
int userId = (int)WebSecurity.CurrentUserId;
if ((this.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
{
WebSecurity.Logout();
Response.RedirectToRoute("Default");
}
}
because I get null reference exception.
EDIT
I used this:
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
int userId = (int)WebSecurity.CurrentUserId;
using (var db = new UsersContext())
{
string s = db.getSessionId(userId);
if ((filterContext.HttpContext.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
{
WebSecurity.Logout();
filterContext.Result = new RedirectResult("/Home/Index");
}
}
}
I had to use using statement for context, otherwise db.getSessionId(userId) was returning old sessionId. Method is this:
public string getSessionId(int userId)
{
string s = "";
var get = this.UserProfiles.Single(x => x.UserId == userId);
s = get.SessionId;
return s;
}
Very strange, will have to read about why that happened.
Everything works fine, except one thing. I have one JsonResult action in a controller, which returns Json, but since event(its textbox on enter event) can't trigger POST(I assume it's because it logs out before) redirect doesn't work. It can't even post to that Json action to receive callback and redirect. Any clues on that?
success: function (data) {
if (data.messageSaved) {
//data received - OK!
}
else {
// in case data was not received, something went wrong redirect out
window.location.href = urlhome;
}
}
Before I used ActionFilterAttribute I used code to check different sessions inside of POST and of course it could make callback and therefore redirect if didn't receive the data.. But now since it can't even POST and go into method it just stucks there and doesn't redirect :)
I would derive from AuthorizeAttribute. No need to check this information if you don't need to authorize the request.
public class SingleLoginAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
int userId = (int)WebSecurity.CurrentUserId;
if ((filterContext.HttpContext.Session.SessionID != dba.getSessionId(userId))
|| dba.getSessionId(userId) == null)
{
WebSecurity.Logout();
isAuthorized = false;
filterContext.Result = new RedirectResult("/Home/Index");
}
}
return isAuthorized;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult()
{
Data = FormsAuthentication.LoginUrl,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
I'd also mention that this allows you to short circuit other ActionFilters because they run after OnAuthorization.
Forward Order - OnAuthorization : AuthorizationFilter (Scope Controller)
Forward Order - OnActionExecuting : ActionFilter1 (Scope Global)
Forward Order - OnActionExecuting : ActionFilter2 (Scope Controller)
Forward Order - OnActionExecuting : ActionFilter3 (Scope Action)
Then as Rob Lyndon mentioned, you could in the FilterConfig (MVC4)
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new SingleLoginAuthorizeAttribute());
}
}
Then when you don't want to require any authorization, you can use the AllowAnonymouseAttribute on your ActionResult methods or Controller Class to allow anonymous access.
Update
I added a way for your ajax calls (Get or Post) to work with timeouts. You can do something like:
success: function (jsonResult)
{
if (jsonResult.indexOf('http') == 0)
{
window.location = jsonResult;
}
// do other stuff with the Ajax Result
}
This isn't exactly the best way, but if you want more information on how to do this better I would ask another question instead of appending more questions on this one.
The ActionFilterAttribute is the way to go.
We created an Action Filter called SeatCheck and decorate each controller like this:
[SeatCheck]
public class NoteController : BaseController
{
We use that to get a count of seats and other functions, but it makes it so much easier to control everywhere without thinking about it.
In the proejct ActionFilters folder we have the SeatCheck.cs file that looks like this:
namespace site.ActionFilters
{
public class SeatCheckAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
You can get the SessionID in the Action Filter like this
filterContext.HttpContext.Session.SessionID
Create a custom action filter, and put that code in the filter, then apply the filter to your controller.
Yes, indeed there is. You can use an attribute derived from ActionFilterAttribute.
I would write a class called SessionSecurityAttribute:
public class SessionSecurityAttribute : ActionFilterAttribute
{
public MyDbConn MyDbConn { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var session = filterContext.RequestContext.HttpContext.Session;
if (session["ID"] != null && WebSecurity.IsAuthenticated)
{
int userId = Convert.ToInt32(session["ID"]);
if ((sessionID != MyDbConn.getSessionId(userId)) || MyDbConn.getSessionId(userId) == null)
{
WebSecurity.Logout();
}
}
}
}
The question remains: how can you add these attributes to your actions whilst giving them access to your database? That's easy: in Global.asax you can call into the bootstrapping RegisterGlobalFilters method:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new SessionSecurityAttribute
{
MyDbConn = DependencyResolver.Current.GetService<MyDbConn>()
});
}
This adds your SessionSecurityAttribute, complete with DB connection, to every action by default, without a line of repeated code.
You might try implementing your own custom ISessionIDManager:
http://msdn.microsoft.com/en-us/library/system.web.sessionstate.isessionidmanager.aspx
In the validate, check to see if it's still valid, otherwise return false.

How can I use an action filter in ASP.NET MVC to route to a different view but using the same URL?

Is it possible to make a filter that, after a controller action has been (mostly) processed, checks for a certain test condition and routes to a different view transparently to the user (i.e., no change in the URL)?
Here would be my best guess at some pseudocode:
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
// If some condition is true
// Change the resulting view resolution to XYZ
base.OnResultExecuting(filterContext);
}
filterContext.Result = new ViewResult
{
ViewName = "~/Views/SomeController/SomeView.cshtml"
};
This will short-circuit the execution of the action.
also you can return view as from your action
public ActionResult Index()
{
return View(#"~/Views/SomeView.aspx");
}
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!
I have extended the AuthorizeAttribute of ASP.NET MVC action filter as DCIMAuthorize, in which I perform some security checks and if user is not authenticated or authorized then action filter will take user to access denied page. My implementation is as below:
public class DCIMAuthorize : AuthorizeAttribute
{
public string BusinessComponent { get; set; }
public string Action { get; set; }
public bool ResturnJsonResponse { get; set; }
public bool Authorize { get; set; }
public DCIMAuthorize()
{
ResturnJsonResponse = true;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
try
{
//to check whether user is authenticated
if (!httpContext.User.Identity.IsAuthenticated)
return false;
//to check site level access
if (HttpContext.Current.Session["UserSites"] != null)
{
var allSites = (VList<VSiteList>)HttpContext.Current.Session["UserSites"];
if (allSites.Count <= 0)
return false;
}
else
return false;
// use Authorize for authorization
Authorize = false;
string[] roles = null;
//get roles for currently login user
if (HttpContext.Current.Session["Roles"] != null)
{
roles = (string[])HttpContext.Current.Session["Roles"];
}
if (roles != null)
{
//for multiple roles
string[] keys = new string[roles.Length];
int index = 0;
// for each role, there is separate key
foreach (string role in roles)
{
keys[index] = role + "-" + BusinessComponent + "-" + Action;
index++;
}
//access Authorization Details and compare with keys
if (HttpContext.Current.Application["AuthorizationDetails"] != null)
{
Hashtable authorizationDetails = (Hashtable)HttpContext.Current.Application["AuthorizationDetails"];
bool hasKey = false;
foreach (var item in keys)
{
hasKey = authorizationDetails.ContainsKey(item);
if (hasKey)
{
Authorize = hasKey;
break;
}
}
}
}
return base.AuthorizeCore(httpContext);
}
catch (Exception)
{
throw;
}
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
try
{
filterContext.Controller.ViewData["ResturnJsonResponse"] = ResturnJsonResponse;
base.OnAuthorization(filterContext);
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
return;
}
if (!Authorize)
{
//Authorization failed, redirect to Access Denied Page
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary{{ "controller", "Base" },
{ "action", "AccessDenied" }
//{ "returnUrl", filterContext.HttpContext.Request.RawUrl }
});
}
}
catch (Exception)
{
throw;
}
}
}
You Can Also Save All Route File Path in a Static And Use it Like This :
public static class ViewPath
{
public const string SomeViewName = "~/Views/SomeViewName.cshtml";
//...
}
And into Your ActionFilter :
context.Result = new ViewResult()
{
ViewName = ViewPath.SomeViewName /*"~/Views/SomeViewName.cshtml"*/
};

Handling the not authorized using custom attributes

I have this custom authorization class to check if a user is an admin:
public class IsAdminAttribute : AuthorizeAttribute
{
private datacontext() db = new datacontext();
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
var currentUser = httpContext.User.Identity.Name;
return db.Users.Where(u => u.UserName == currentUser).Where(ut => ut.UserTypeID == 2).Count() == 1 ? true : false;
}
return isAuthorized;
}
}
and is used here:
[IsAdmin]
public ActionResult CreateUser()
{
ViewBag.UserTypeID = new SelectList(db.UserTypes, "UserTypeId", "Name");
return View();
}
and works ok but takes me back to my log in page when the user is not authorized. What I want to happen is for the user to be redirected somewhere with an error message popping up. How do I handle the denied access event?
How do I handle the denied access event?
Simply override the HandleUnauthorizedRequest method and return directly the view you like:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new ViewResult
{
ViewName = "Unauthorized"
};
}
This will render ~/Views/Shared/Unauthorized.cshtml. You could also pass view models, master pages, etc... to this ViewResult.

How to handle Session timeout in MVC 3

I am having issues with frequent Session Time Out.
I want to write a common filter that I could use on each controller, filter should redirect the user to login and after log in back to from where user sent the last request.
You could try something like this:
public class SessionExpireAttribute : ActionFilterAttribute {
public override void OnActionExecuted(ActionExecutedContext filterContext) {
base.OnActionExecuted(filterContext);
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (filterContext.HttpContext.Session != null) {
if (filterContext.HttpContext.Session.IsNewSession) {
var sessionCookie = filterContext.HttpContext.Request.Headers["Cookie"];
if ((sessionCookie != null) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0)) {
// redirect to login
}
}
}
}
}
There's more here than meets the eye. Here's a more complete OnActionExecuting that uses the same concept already discussed above but adds a bit more. See inline comments for more info. The "InitializeSession" being called is a custom function which creates the basic attributes needed in Session State for running the site. "AlertWarning" is a Helper routine for displaying alerts. Everything else is boilerplate code.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var bRequiresAuthorization =
(filterContext.ActionDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), false).Length > 0) ||
(filterContext.Controller.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), false).Length > 0);
if (filterContext.HttpContext.Session != null)
{
if (filterContext.HttpContext.Session.IsNewSession)
{
//New session. Initialize Session State
bool b = InitializeSession(null);
if (bRequiresAuthorization )
{
//Action requested requires authorized access. User needs to authenticate this
//new session first, so redirect to login
string cookie = filterContext.HttpContext.Request.Headers["Cookie"];
if ( (cookie != null) && (cookie.IndexOf("_SessionId=") >= 0) )
{
//An expired session cookie still resides on this PC, so first alert user that session is expired
AlertWarning("Session timed out due to inactivity. Please log in again.");
}
filterContext.Result = RedirectToAction("LogOut", "Authentication");
}
}
}
base.OnActionExecuting(filterContext);
}
Have you tried the existing Authorize filter?
as mentioned above .. try this
public class SessionExpireAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (filterContext.HttpContext.Session != null) {
if (filterContext.HttpContext.Session.IsNewSession) {
filterContext.Result = new RedirectResult("/");//redirect to home page
}
}
}
}
and then apply this filter over the action or controller [SessionExpire]

Resources