Preventing OnActionExecuting and filtering - asp.net-mvc

I have this filter class.
public class Sessional : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpSessionStateBase session = filterContext.HttpContext.Session;
LoggedUserInfo user = (LoggedUserInfo)session["User"];
if ((user == null && !session.IsNewSession) || (session.IsNewSession))
{
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
string loginUrl = urlHelper.Content("~/Account/LogOut");
FAuth.AbandonSession();
FormsAuthentication.SignOut();
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
}
}
When I apply it on the Controller, all the action will logout user if session is unavailable.
But I want to write attribute that allow the only action to do its work without loggin out, for example UnSessional.
[Authorize]
[Sessional]
public class ReportController : Controller
{
[HttpGet]
[UnSessional]
public ActionResult GetReport() //unsessional action
{
return View();
}
[HttpPost]
public ActionResult GetReport(GetReportModel model) //sessional action
{
if (!ModelState.IsValid)
{
return View();
}
return View();
}
}

You can check for the "UnSessionAttribute" existance for the current Action, here is the sample code:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(filterContext.ActionDescriptor.GetCustomAttributes(typeof(UnSessionAttribute), true).Length > 0)
{
return;
}
HttpSessionStateBase session = filterContext.HttpContext.Session;
LoggedUserInfo user = (LoggedUserInfo)session["User"];
if ((user == null && !session.IsNewSession) || (session.IsNewSession))
{
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
string loginUrl = urlHelper.Content("~/Account/LogOut");
FAuth.AbandonSession();
FormsAuthentication.SignOut();
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
}

Related

Custom Authentication for different areas in mvc

I have a MVC project with 2 areas: Admin and Client. I also have a login page in the main controller. What I want to do is to Authenticate a user based on its roles. If the user is for client they can't login to admin and the other way around.
For example if you try Localhost/admin, the code checks if the user is authorised. If not it redirects you to Localhost/admin/AccountLogin. The same for Localhost/client to Localhost/client/account/login.
I want to use a customAuthorize rather than [Authorize(Roles="Admin")].
everything works fine if I don't use roles, but the problem is if you login as client you can simply change the url and go to admin. So I tried to use roles.
In admin area:
An account Controller:
public class AccountController : MainProject.Controllers.AccountController
{ }
A home controller:
[CustomAuthorize("Admin")]
public class HomeController : Controller
{
public ActionResult HomePage()
{
return View();
}
}
The custom Authorise:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private string _loginPage { get; set; }
private string _customRole { get; set; }
public CustomAuthorizeAttribute(string userProfilesRequired)
{
_customRole = userProfilesRequired;
_loginPage = "/" + _customRole + "/Account/Login";
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var formsIdentity = filterContext.HttpContext.User.Identity as System.Web.Security.FormsIdentity;
// I want to check if the role of current user is the same as the controller If not redirect to the /account/login page.
var validRole = this.Roles == _customRole;//filterContext.HttpContext.User.IsInRole(_customRole);
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (!validRole)
{
filterContext.HttpContext.Response.Redirect(_loginPage);
}
}
else
{
filterContext.HttpContext.Response.Redirect(_loginPage);
}
base.OnAuthorization(filterContext);
}
}
The Account Controller in Main Controller:
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string ReturnUrl)
{
try
{
if (ModelState.IsValid)
{
if (model.UserName == "Arash" && model.Password == "123")
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
//I need to set the roles here but not sure how
return RedirectToAction("homePage", "Home", new { area = GetArea() });
}
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
catch (Exception ex)
{
ModelState.AddModelError("", "Error: " + ex.Message);
return View(model);
}
}
}
and it the web config:
<forms loginUrl="~/Account/Login" timeout="200" />
</authentication>
<authorization>
<allow roles="Admin,Client" />
</authorization>
I searched a lot in the web but couldn't find a proper answer. I appreciate if you Could help me out to correctly implement this authorisation in MVC.
I just want to know how can I set a role to a user when login. At the moment if I set a user in login, it can't remember when it gets to CustomAuthorize class.
Any help?
Cheers,
There are a lot of ways to this but I will tell you what I used in this case.
You don't actually need to create a custom Authorization Attribute but instead make use of PostAuthenticateRequest event Handler in Global.asax given that you have a "table" roles in your database.
Add the code below in Global.asax
public override void Init()
{
this.PostAuthenticateRequest += new EventHandler(MvcApplication_PostAuthenticateRequest);
base.Init();
}
void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated && User.Identity.AuthenticationType == "Forms")
{
string[] roles = GetRoleOfUser(Context.User.Identity.Name);
var newUser = new GenericPrincipal(Context.User.Identity, roles);
Context.User = Thread.CurrentPrincipal = newUser;
}
}
public string[] GetRoleOfUser(string username)
{
string[] usersInRole;
// Get the Role of User from the Database
// Should be of String Array
// Example Query to Database: 'Select UserRole FROM User WHERE Username = "arash"'
// It doesnt matter if user has One or more Role.
return usersInRole;
}
Then your account controller should be this.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string ReturnUrl)
{
try
{
if (ModelState.IsValid)
{
if (model.UserName == "Arash" && model.Password == "123")
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
return RedirectToAction("HomePage", "Home");
}
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
catch (Exception ex)
{
ModelState.AddModelError("", "Error: " + ex.Message);
return View(model);
}
}
Now for example there is an Action in your HomeController that can only be access by Admin. You can just decorate the action with Authorize attribute like this below.
HomeController.cs
[Authorize(Roles = "Admin")]
public ActionResult AdminHomepage()
{
//For Admin Only
return View();
}
[Authorize(Roles = "Client")]
public ActionResult ClientHomepage()
{
//Client only Homepage, User with Role "Admin" cant go here.
return View();
}
[AllowAnonymous]
public ActionResult HomePageForAll()
{
//For Everyone
return View();
}
[Authorize(Roles = "Client,Admin")]
public ActionResult HomePageForClientAndAdmin()
{
return View();
}
public ActionResult HomePage()
{
return View();
}
The user will be redirected to Login URL if they are not authorized given that it is specified in Web.config (Which you already have set).
I have an action method and that can be accessed by Admin only
// Action Methods
[AuthorizationService] // My custom filter ,you can apply at controller level
public ActionResult ProjectList(Employee emp)
{
// do some work
}
//Employee class
public class Employee
{
string Name{get;set;}
string Role{get;set;}
}
// My custom filter
class AuthorizationService : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Employee = filterContext.ActionParameters["emp"] as Employee;
if (Employee.Role!="Admin")
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new { action = "Login", Controller ="Home"}));
}
}
}

MVC 4 Redirect User After LogIn

How can I redirect user after successful login to specific page. I need to do this by Authorize Attribute. In my problem I have to redirect user to page: Index/About when he/she would use specific login name, rest of users would be redirected to Index/Home.
I try this method:
public class AuthAttribute : AuthorizeAttribute
{
private string userName;
protected override bool AuthorizeCore(HttpContextBase filterContext)
{
var authorized = base.AuthorizeCore(filterContext);
if (!authorized)
{
return false;
}
string userAuthenticated = filterContext.User.Identity.Name;
bool specialUser = userName.Equals(userAuthenticated, StringComparison.OrdinalIgnoreCase);
if (specialUser)
{
filterContext.Items["RedirectToHomeAbout"] = true;
return false;
}
return true;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.HttpContext.Items.Contains("RedirectToHomeAbout"))
{
var routeValues = new RouteValueDictionary(new
{
controller = "Home",
action = "About",
});
filterContext.Result = new RedirectToRouteResult(routeValues);
}
else
{
base.OnAuthorization(filterContext);
}
}
This is the code from account controller:
[Authorize]
[InitializeSimpleMembership]
public class AccountController : Controller
{
//
// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
//
// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
WebSecurity.Logout();
return RedirectToAction("Index", "Home");
}
Thanks!
MarQ
Instead of override OnAuthorization, you might want to override HandleUnauthorizedRequest.
In this way, the code would look like:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) {
if (filterContext.HttpContext.Items.Contains("RedirectToHomeAbout"))
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "About" },
{ "controller", "Home" }
});
}
else
{
//your general redirect here
}
}
If I understand your requirement correctly you can do it in your AccountController:
private string specificUserName = "specificUserName";
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
if (string.Equals(model.UserName, specificUserName))
{
return RedirectToAction("Index", "About");
}
return RedirectToAction("Index", "Home");
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}

Redirect to a certain view in AuthorizeAttribute?

In my AdministratorController I have an action with custom attribute:
[AuthorizedOnly (Roles = "admin, superadmin")]
public ActionResult Index()
{...}
The attribute is:
class AuthorizedOnlyAttribute : AuthorizeAttribute
{
public AuthorizedOnlyAttribute()
{
View = "~/Views/Main/Index.cshtml";
Master = String.Empty;
}
public String View { get; set; }
public String Master { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
CheckIfUserIsAuthenticated(filterContext);
}
private void CheckIfUserIsAuthenticated(AuthorizationContext filterContext)
{
if(filterContext.Result == null)
return;
if(filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if(String.IsNullOrEmpty(View))
return;
var result = new ViewResult{ViewName = View, MasterName = Master};
filterContext.Result = result;
}
}
It correctly shows me the view that I need: ~/Views/Main/Index.cshtml
But in my browser URL is still from Administrator controller: .../Administrator/Index
How can I redirect to the View that I need, so that URL would also change?
Thanks a lot in advance!
Try this
string retUrl = filterContext.HttpContext.Request.RawUrl;
filterContext.Result =
new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary
{{ "controller", "Main" },
{ "action", "Home" },
{ "returnUrl", retUrl } });

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"*/
};

deny custom role

how can i deny access to call method. something like this
[HandleError]
[Authorize(Roles = "role1, role2")]
public class AdminController : Controller
{
[Deny(Roles = "role2")]
public ActionResult ResultPage(string message)
{
ViewData["message"] = message;
return View();
}
}
You could simply do it the other way around and check for the presence of role1 instead of the absence of role2. Alternatively you could develop your own DenyAttribute that does what you want and verifies that the user is not in the specified role.
[HandleError]
[Authorize(Roles = "role1, role2")]
public class AdminController : Controller
{
[Authorize(Roles = "role1")]
public ActionResult ResultPage(string message)
{
ViewData["message"] = message;
return View();
}
}
public class DenyAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated) {
return false;
}
if (Users.Length > 0 && Users.Split(',').Any( u => string.Compare( u.Trim(), user.Identity.Name, StringComparer.OrdinalIgnoreCase))) {
return false;
}
if (Roles.Length > 0 && Roles.Split(',').Any( u => user.IsInRole(u.Trim()))) {
return false;
}
return true;
}
}

Resources