Unable to pass data form SecurityAttribute class to controller in MVC3 - asp.net

i am using mvc 3 code first. facing problem while passing data form SecurityAttribute class to Controller. i actually want to redirect user on login page with displaying Message. for this i override AuthorizeCore method in SecurityAttribute class. in this method i am unable to direct use session, cookies, tempdate, and viewbag etc. any other solution to solve this problem. Thanks
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Session["UserID"] == null)
{
//here i am unable to pass message to User/LogOn action.
httpContext.Response.Redirect("~/User/LogOn");
// httpContext.Session["lblMsg"] = "You are not authroize to perform this action.Please Login through different account";
return false;
}

First things first, you should not redirect inside the AuthorizeCore method. You should use the HandleUnauthorizedRequest method which is intended for this purpose. As far as passing an error message to the LogOn action is concerned you could use TempData:
public class SecurityAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// perform the custom authorization logic here and return true or false
// DO NOT redirect here
return httpContext.Session["UserID"] != null;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Controller.TempData["ErrorMessage"] = "You are not authroize to perform this action.Please Login through different account";
// calling the base method will actually throw a 401 error that the
// forms authentication module will intercept and automatically redirect
// you to the LogOn page that was defined in web.config
base.HandleUnauthorizedRequest(filterContext);
}
}
and then inside the LogOn action:
public ActionResult LogOn()
{
string errorMessage = TempData["ErrorMessage"] as string;
...
}
or if you want to access it inside the LogOn.cshtml view:
<div>#TempData["ErrorMessage"]</div>
Another possibility is to pass the message as a query string parameter instead of using TempData:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var values = new RouteValueDictionary(new
{
controller = "User",
action = "LogOn",
errormessage = "You are not authroize to perform this action.Please Login through different account"
});
filterContext.Result = new RedirectToRouteResult(values);
}
and then you could have the LogOn action take the error message as action parameter:
public ActionResult LogOn(string errorMessage)
{
...
}

Related

handling sessions on every page of website

I have a data driven website and the current users Id gets stored in Session["UserId"].So all the data that shows up in almost all the pages is user specific.and when a user is using the site anonymously,it is a different set of results that i show and has nothing to do with the UserId.
My problem is I have to check if the Session["UserId"] is not null at every line where I am using Session["UserId"] and i somehow feel that it is not the right way to do it.
Is there a way where I can check if the Session is not null on page_load? If my session turns out to be null, how do i handle it? the page won't even load at all.
I hope i was able to explain
Instead of check session on every of your pages, put the session control in a base class and make all your pages extends this class. Every time your page inits the Page_Init base method will check if user is authenticated. If it's not authenticated the method will throw an exception that will be catched by Page_Error method. This method will clear session resources and redirect to Default page.
Make a hyerarchical classes for session control:
public class UserSession { }
public class AnonymousSession : UserSession {}
On your Page Logon put the UserId on the session based on logon type:
bool isAnon = GetAnonymous(); // Check form page if login is anonymously
UserSession user;
if(isAnon)
user = new AnonymousSession();
else
user = new UserSession();
Session.Contents.Add("UserId", user);
Set a property in PageBase named Anonymously that tells you if user has entered anonymously, and use it in your pages to set the set results of each of your pages:
public class PageBase: System.Web.Ui.Page
{
// Check here if session type is anonymous
protected bool Anonymously
{
get
{
return (UserSession)Session.Contents["UserId"] is AnonymousSession;
}
}
protected void Page_Init(object Sender,System.EventArgs e)
{
var user = (UserSession)Session.Contents["UserId"];
if (user == null)
{
throw new SessionException();
}
}
protected void Page_Error(object sender, System.EventArgs e)
{
Exception ex = Server.GetLastError();
Server.ClearError();
if(ex is SessionException)
{
Context.Session.Clear();
Context.Session.Abandon();
FormsAuthentication.SignOut();
Server.Transfer("Default.aspx", true);
}
}
}

Asp.Net MVC5 How to ensure that a cookie exists?

I'm new to MVC (5). In order to add localization support to my website I added a "Language" field to my ApplicationUser : IdentityUser
What's the best approach to now store this information in the browser and ensure that it gets re-created even if the user manually deletes it?
TL; but I've got time
What I've tried until now:
I started creating a cookie in my method private async Task SignInAsync(ApplicationUser user, bool isPersistent) but I notice that:
This method is not used if the user is already authenticated and automatically logs in using the .Aspnet.Applicationcookie and my language cookie could be meanwhile expired (or been deleted).
A user could manually delete the cookie, just for fun.
I thought about checking its existence in the controller (querying the logged user and getting it from the db) and it works but I'd need to do it in EVERY controller. I'm not sure is the correct way to do this.
Any suggestion about how to approach this problem and guarantee that the application has a valid "language cookie" on every request?
It sounds to me like what you want here is a Custom Action Filter. You can override the OnActionExecuting method which means the logic is run before any action is called
public class EnsureLanguagePreferenceAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var langCookie = filterContext.HttpContext.Request.Cookies["LanguagePref"];
if (langCookie == null)
{
// cookie doesn't exist, either pull preferred lang from user profile
// or just setup a cookie with the default language
langCookie = new HttpCookie("LanguagePref", "en-gb");
filterContext.HttpContext.Request.Cookies.Add(langCookie);
}
// do something with langCookie
base.OnActionExecuting(filterContext);
}
}
Then register your attribute globally so it just becomes the default behaviour on every controller action
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new EnsureLanguagePreferenceAttribute());
}
To me, the easiest way would be to create your own Authorize attribute (since your language options are tied to an authenticated user account). Inside of your new authorize attribute, simply perform the check if the cookie exists. If it does, then life is good. Else, query the user's database profile and reissue the cookie with the stored value
public class MyAuthorization : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//no point in cookie checking if they are not authorized
if(!base.AuthorizeCore(httpContext)) return false;
var cookie = httpContext.Request.Cookies["LanguageCookie"];
if (cookie == null) {
CreateNewCookieMethod();
}
return true;
}
}
To use, replace [Authorize] with [MyAuthorization] in your project.
If you don't want to mess with the [Authorize] attribute, you could create your own attribute that does the cookie checking and decorate your controller with that one as well.
One last alternative is to create your own Controller class that does the checking on the OnActionExecuting.
public class MyBaseController : Controller
{
public string Language {get;set;}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var cookie = filterContext.HttpContext.Request.Cookies["LanguageCookie"];
if(cookie == null){
cookie = CreateNewCookieMethod();
filterContext.HttpContext.Request.Cookies.Add(cookie);
}
Language = cookie.Value;
base.OnActionExecuting(filterContext);
}
How to use (note that we inherit from MybaseController now)
public class HomeController : MyBaseController{
public ActionResult Index(){
//Language comes from the base controller class
ViewBag.Language = Language;
Return View();
}
}
This method is neat because now that Language variable will be available in any controller that inherits from this new class.
Either of these will give you a single, cookie checking point. Additionally, you are only going back to the database only in the instance that the cookie does not exist.

Customized validation for AuthorizeAttribute keeps the process running

I have the following customized validator :
public class CAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//if ajax request set status code and end responcse
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.Write(/*some data*/);
filterContext.HttpContext.Response.End();
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
and the following Action
[HttpPost]
[CAuthorize]
public ActionResult Comment(Comment comment_obj)
{
if (ModelState.IsValid)
{
this.tblSomthing.comments.Add(comment_obj);
this.db.SaveChanges();
}
return /*some view*/
}
why when the validator fails the action is executed and the DB record is saved but the response is correct (the one that is set in the validator)
I just want to stop the execution of the action if the validator fails.
You know that an action needs to return a view in order the user sees some form of output? Using Response.Write or Response.End in a controller does nothing.
Anyway what you have there is an attribute, not a validator.
If it were a validator it'd be inside the model used in the view, applied to a property.

Asp.Net MVC Authentication Roles without Providers

I know this has been answered here before, however even after following all the solutions I could find, I cannot still get my roles working in my system.
I have a Asp.Net MVC application, with Forms based authentication. Instead of using a local database, it uses OpenAuth/OpenID for authentication, and a database lookup table for application roles.
As per main suggestion, I implemented the roles in Global.asax like:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
//Fires upon attempting to authenticate the use
if (HttpContext.Current.User != null &&
HttpContext.Current.User.Identity.IsAuthenticated &&
HttpContext.Current.User.Identity.GetType() == typeof (FormsIdentity))
Thread.CurrentPrincipal = HttpContext.Current.User = OpenAuthPrincipal.Get(HttpContext.Current.User.Identity.Name);
}
Here OpenAuthPrincipal.Get is a very straightforward static method wrapping the openauth id with the roles:
public static IPrincipal Get(string userId)
{
var db = new WebData();
var user = db.Users.Find(userId);
return new GenericPrincipal(new Identity(user), user.Roles.Split('|'));
}
However when I reach a function like:
[Authorize(Roles = "Admin")]
public ActionResult Edit(int id)
{
...
}
It fails. If I remove the Roles restriction, and check User.IsInRole("Admin") in the debugger I get a false. However, if I do the check in the Global.asax, I get true.
I know that the User.Identity.Name is coming correctly. And also the IIdentity is not modified at all. However only the roles are lost.
What could be the cause of this issue?
Update:
The solution recommended below did not directly work, however this change fixed the issue for me:
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
httpContext.User = OpenAuthPrincipal.Get(httpContext.User.Identity.Name);
return base.AuthorizeCore(httpContext);
}
As per main suggestion, I implemented the roles in Global.asax like:
No idea where did you get this main suggestion from but in ASP.NET MVC you normally use authorization action filters. And since the default Authorize filter doesn't do what you need, you write your own:
public class OpenIdAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (authorized)
{
httpContext.User = OpenAuthPrincipal.Get(httpContext.User.Identity.Name);
}
return authorized;
}
}
and then:
[OpenIdAuthorize(Roles = "Admin")]
public ActionResult Edit(int id)
{
...
}

ASP.Net MVC 3 Strange Session Behaviour

I have an mvc 3 app for which I'm implementing authorization using my own login view which checks if the users name and password are allowed and then sets a variable in the session to say that the user is loggged in. This kind of works but for one particular view it is behaving in a strange undesirable way. The said view contains a form which I use to input some data and upload a file. For some reason which I can't figure out, after this form is posted a new session is started and therefore the variable which remembered that the user was logged in is reset to false and subsequently the login page is displayed again.
I'm lost as to why the application is starting a new session at this point? I have not instructed it to do this. Can anyone recommend solutions to stop this behaviour and get it to keep the old session?
Thanks.
UPDATE - Some Code:
Note the session seems to be terminated immediately after the response to the posted Create form
CMS controller which uses a custom Autorize attribute called "RDAutorize" on all actions:
[RDAuthorize]
public class PhotoCMSController : Controller
{
public ActionResult Create()
{
/* Code omitted: set up a newPhoto object with default state */
/* Display view containing form to upload photo and set title etc. */
return View("../Views/PhotoCMS/Create", newPhoto);
}
[HttpPost]
public ContentResult Upload(int pPhotoId)
{
/* Code ommited: receive and store image file which was posted
via an iframe on the Create view */
string thumbnail = "<img src='/path/to/thumb.jpg' />";
return Content(thumbnail);
}
[HttpPost]
public ActionResult Create(string pPhotoTitle, string pCaption etc...)
{
/*Code omitted: receive the rest of the photo data and save
it along with a reference to the image file which was uploaded
previously via the Upload action above.*/
/* Display view showing list of all photo records created */
return View("../Views/PhotoCMS/Index", qAllPhotos.ToList<Photo>());
/* **Note: after this view is returned the Session_End() method fires in
the Global.asax.cs file i.e. this seems to be where the session is
being lost** */
}
}/*End of CMS Controller*/
Custom Authorize action filter:
public class RDAuthorize : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Boolean authorized = Convert.ToBoolean(
HttpContext.Current.Session["UserIsAuthorized"]
);
if (!authorized) {
/* Not logged in so send user to the login page */
filterContext.HttpContext.Response.Redirect("/Login/Login");
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext) {}
public override void OnResultExecuting(ResultExecutingContext filterContext) {}
public override void OnResultExecuted(ResultExecutedContext filterContext) {}
}/*End of Authorize Action Filter*/
Login controller:
public class LoginController : Controller
{
private PhotoDBContext _db = new PhotoDBContext();
public ActionResult Login()
{
string viewName = "";
Boolean authorized = Convert.ToBoolean(Session["UserIsAuthorized"]);
if (authorized)
{
viewName = "../Views/Index";
}
else
{
viewName = "../Views/Login/Login";
}
return View(viewName);
}
[HttpPost]
public ActionResult Login(string pUsername, string pPassword)
{
string viewName = "";
List<Photo> model = new List<Photo>();
var qUsers = from u in _db.Users
select u;
foreach (User user in qUsers.ToList<User>())
{
/* If authorized goto CMS pages */
if (pUsername == user.Username && pPassword == user.Password)
{
Session["UserIsAuthorized"] = true;
var qPhotos = from p in _db.Photos
where p.IsNew == false
select p;
model = qPhotos.ToList<Photo>();
viewName = "../Views/PhotoCMS/Index";
break;
}
}
return View(viewName, model);
}
}/* End of Login controller */
Turns out the whole ASP.Net application was restarting because as part of the photo upload I was storing the image file in a temporary folder and then deleting the directory after moving the file to a permanent location. Apparently its default behaviour for ASP.Net to restart if a directory within the web site is deleted. I found this post
which describes the problem and offers a solution whereby the following code is added to the Global.asax.cs file. Implementing this solution has fixed the problem. The fix is applied by calling FixAppDomainRestartWhenTouchingFiles() from the Application_Start() event:
protected void Application_Start()
{
FixAppDomainRestartWhenTouchingFiles();
}
private void FixAppDomainRestartWhenTouchingFiles()
{
if (GetCurrentTrustLevel() == AspNetHostingPermissionLevel.Unrestricted)
{
/*
From: http://www.aaronblake.co.uk/blog/2009/09/28/bug-fix-application-restarts-on-directory-delete-in-asp-net/
FIX disable AppDomain restart when deleting subdirectory
This code will turn off monitoring from the root website directory.
Monitoring of Bin, App_Themes and other folders will still be
operational, so updated DLLs will still auto deploy.
*/
PropertyInfo p = typeof(HttpRuntime).GetProperty(
"FileChangesMonitor", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
object o = p.GetValue(null, null);
FieldInfo f = o.GetType().GetField(
"_dirMonSubdirs", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
object monitor = f.GetValue(o);
MethodInfo m = monitor.GetType().GetMethod(
"StopMonitoring", BindingFlags.Instance | BindingFlags.NonPublic);
m.Invoke(monitor, new object[] { });
}
}
private AspNetHostingPermissionLevel GetCurrentTrustLevel()
{
foreach (AspNetHostingPermissionLevel trustLevel in
new AspNetHostingPermissionLevel[] {
AspNetHostingPermissionLevel.Unrestricted,
AspNetHostingPermissionLevel.High,
AspNetHostingPermissionLevel.Medium,
AspNetHostingPermissionLevel.Low,
AspNetHostingPermissionLevel.Minimal }
)
{
try
{
new AspNetHostingPermission(trustLevel).Demand();
}
catch (System.Security.SecurityException)
{
continue;
}
return trustLevel;
}
return AspNetHostingPermissionLevel.None;
}
Since sessions are associated with cookies, they are available for a specific domain.
It's a common mistake to ask for a session variable in the same application while the domain has changed (i.e. redirecting to a subdomain).
Does the controller action that you are posting the form contains any [Authorize] attribute. You need to post some code.
Verify a new session is really started every time. Check Trace output for the user's session id to ensure it realllly has changed.
Ensure the cookie getting sent over is actually getting set and sent over. (called ASPsessionIDSOMETHING ) and if that is being sent by the browser. Download the tool Fiddler to check the cookies easily (set cookie header coming from the server and the request cookies going back to the server from the browser. Make sure your browser is accepting the cookie and you dont say... have cookies turned off.
If your session id is changing at every request then your session isn't properly getting set the first time, set a break point on that code if you havent already.
You can log when the worker process resets - ensure that isn't the case. see http://www.microsoft.com/technet/prodtechnol/windowsserver2003/library/IIS/87892589-4eda-4003-b4ac-3879eac4bf48.mspx
I had the same problem. The problem only occured when a post request was send to the server and the session was not modified during that request. What I did as a workaround was, to write a custom filter which does nothing more than writing a key / value into the session on each request and added that filter to the GlobalFilter collection in the global.asax.
public class KeepSessionAlive : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if(filterContext.HttpContext.Session != null)
{
filterContext.HttpContext.Session["HeartBeat"] = DateTime.Now.ToShortDateString();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext) { }
}
And in the global.asax:
protected override void AddCustomGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new KeepSessionAlive());
}
This might be not the best solution but it helped me in my case.

Resources