Technologies I'm Using:
MVC v2
Forms Authentication (Sliding Expiration)
Session State Server
Custom Authorization Attribute
I'm using the state server process for my mvc app. During testing, when an authenticated user would click the "LogOff" button, it would correctly take them to the authentication screen, and upon successful credential entering, would log them back in. BUT, it would find their prior session variable state, and NOT reload any new permissions I'd given them. This is due to how I'm loading a user in the following code:
public override void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null)
throw new ArgumentNullException("FilterContext");
if (AuthorizeCore(filterContext.HttpContext)) {
IUser customUser = filterContext.HttpContext.Session["CustomUser"] as IUser;
if ((customUser == null) || (customUser.Name != filterContext.HttpContext.User.Identity.Name)) {
customUser = new User(filterContext.HttpContext.User.Identity.Name,
filterContext.HttpContext.User.Identity.IsAuthenticated);
}
if (_privileges.Length > 0) {
if (!customUser.HasAtLeastOnePrivilege(_privileges))
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
filterContext.HttpContext.Session["CustomUser"] = customUser;
}
}
So, you can see I'm storing my customUser in the Session and that value is what was fetched from the prior session even though the user had logged off between (but logged back on within the sliding expiration window.
So, my question is, should I place a simple Session.Abandon() call in my LogOff method in the AccountController, or is there a cleaner more advantageous way of handling this?
Normally Session.Clear() should be enough and remove all values that have been stored in the session. Session.Abandon() ends the current session. It might also fire Session_End and the next request will fire Session_Start.
Related
We have an ASP.NET MVC 4 intranet application. We’re using Windows Authentication and that aspect works fine. The user’s credentials are used and we can access those credentials from the web app.
What we really want is some sort of hybrid mode, however. We want to get the user’s credentials from the browser, but we also want to verify that the user is in our application’s database. If the user’s in the database, then they can just continue on. If they’re not, we want to redirect them to a page asking for alternate credentials. What I’m doing now is, in Global.asax.cs, I’ve got an Application_AuthenticateRequest method and I’m checking to see if the user is authenticated. If they are and their cookie information doesn’t reflect the fact that they’re logged into the system, then I log them in and set up some cookies with info about the user. If they’re not authenticated, I redirect them to a login page. We can’t use AD roles for reasons involved with company policy, so we need to use the database for additional authentication.
I’m guessing Application_AuthenticateRequest isn’t the place to do this, but maybe it is. But we basically need a place to filter the requests for authentication. But additionally this implementation leads me to another issue:
We have certain URLs in our app that allow anonymous access. I’ve added <location> tags to the web.config for these. The problem is, when anonymous calls are made into these, it gets to Application_AuthenticateRequest and tries to log the user into the DB. Now, I can add code into Application_AuthenticateRequest to handle these URLs and that’s currently my plan, but if I’m write and Application_AuthenticateRequest isn’t the place to be doing this, then I’d rather figure it out now than later.
You need to use Action Filters for this purpose. You can extend the AuthorizeAttribute like this:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private UnitOfWork _unitOfWork = new UnitOfWork();
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = false;
var username = httpContext.User.Identity.Name;
// Some code to find the user in the database...
var user = _unitOfWork.UserRepository.Find(username);
if(user != null)
{
isAuthorized = true;
}
return isAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (AuthorizeCore(filterContext.HttpContext))
{
SetCachePolicy(filterContext);
}
else
{
// If not authorized, redirect to the Login action
// of the Account controller...
filterContext.Result = new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary {
{"controller", "Account"}, {"action", "Login"}
}
);
}
}
protected void SetCachePolicy(AuthorizationContext filterContext)
{
// ** 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(CacheValidationHandler, null /* data */);
}
public void CacheValidationHandler(HttpContext context,
object data,
ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
Then, you can use this attribute at the Controller level or Action level like this:
[MyAuthorize]
public ActionResult SomeAction()
{
// Code that is supposed to be accessed by authorized users only
}
I've got the remember me option available on the login page. Could somebody please explain the process? i.e. is going through the logging in process every time user navigates to the page? or is the user constantly logged in and the application only checks the credentials when the user logs off and in again? The reason I ask is that I have IsEnabled property on the user table in DB and would like to disable users. But this property doesn't seem to make any difference unless user logs off and in again.
Any Ideas?
Thanks.
When the user logs in for the first time with valid credentials, a cookie is created and stored client side. With each subsequent request to the website the cookie is passed to the server and validated to ensure it has not expired and is valid. If you want to be able to check if your IsEnabled should allow a user to access the site, you need to create your own authenticatin logic in the Application_AuthenticateRequest event in your Global.asax file.
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
// Your authentication logic
}
You can view a full list of events you can hook into here: http://www.dotnetcurry.com/ShowArticle.aspx?ID=126
I have used this variant
Add this attribute to your controller
public class IsLocked : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAuthenticated)
return false;
var session = DependencyResolver.Current.GetService<ISession>();
var userDb = session.Query<Admin>().SingleOrDefault(x => x.Email == httpContext.User.Identity.Name);
if (userDb == null)
return false;
return userDb.Status == null || userDb.Status.Value == false;
}
}
I have an Intranet application with Windows authentication set for user authentication which works fine, only problem is that I do not want to say 'Hello, mydomain\user!' but use the user's full display name which I find in the Active Directory.
In fact I want to populate the profile with even more details from our domain, the problem is that I only want to do this AD query only once after the user has been authenticated on his first call to the application. I have all the AD and profile things working, but I do not find a good place to put the code so that it is called exactly once after login. I suspect a custom AuthorizeAttribute might be a way... Any help is greatly appreciated. Thanks!!
Try storing the information in session or within cookies or local storage on the client side.
Well, I finally came up with a solution - can this be considered as a as a valid answer? Basically I wrote a custom AuthorizationFilter and put a flag into the session to do the whole work only once. However I hoped to find an event "User_Authenticated" which is fired only once. But I guess this is more appropriate for Forms authentication.
public class ProfileUpdater : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// if there is a profile already in the session we do not update this
Controller controller = filterContext.Controller as Controller;
if (controller != null && controller.Session["ProfileUpdated"] != null)
{
return;
}
else if (controller == null)
{
return;
}
UserPrincipal domainUser = DomainHelper.GetDomainUser(controller.User.Identity.Name);
if (domainUser != null)
{
controller.Profile.SetPropertyValue("DisplayName", domainUser.DisplayName);
controller.Session["ProfileUpdated"] = true; // just put a marker object into the session to show we alreay updated the Profile
}
return;
}
}
Scenario
I develop an MVC application that will be hosted in Windows Azure. During development, I test against a local database and local user membership service. In production, the application hits off a SQL Azure database and a cloud-hosted user membership service.
Sometimes I'll log in to the local version of the site with a user account that only exists on my local machine. If I forget to log out but switch my machine out of test mode, things start to break.
The Problem
I set my hosts file to point my application URL at 127.255.0.0 so that browser requests are handled by the Azure emulator locally. This also means my test application and the production application use the same host name - cookies for one are seen as valid cookies for the other.
If I log in to my local system, an ASPXAUTH cookie is created for the domain and contains the credentials of the user from my local system. If I forget to log out and switch my hosts file back to normal (browser requests go to the live application), it sends the same ASPXAUTH cookie to the server.
Since this user doesn't exist on the server, only locally, any requests like Membership.GetUser().Email return an object null exception.
What I'd Like to Do
Ideally, I could inject some code early in the request to check that the user exists. Something like:
MembershipUser user = Membership.GetUser();
if(user == null) {
FormsAuthentication.SignOut();
}
This will automatically remove the ASPXAUTH cookie for invalid users. But where do I put it? I started looking at my controllers - all of them inherit from a BaseController class, but even putting this code in the base controller's constructor isn't early enough. I also looked at Global.asax, but I'm not sure which event would be best to tie this action to.
Also, I'm not even sure this is the right way to do things. If not, what is the best way to ensure that the authentication cookie is for a valid user before just depending on the membership class like I am currently?
You may handle the below event in you global.aspx:
public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
{
}
EDIT:
In the above method, Membership.GetUser() will return null, since it takes the userName from HttpContext.Current.User.Identity.Name property and in this event the user has not yet been authenticated. The above method is invoked by FormsAuthenticationModule when it raised Authenticate even. You can handle this event like so:
public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported &&
Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
var formsAuthTicket = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value);
MembershipUser user = Membership.GetUser(formsAuthTicket.Name);
if (user == null)
{
FormsAuthentication.SignOut();
Request.Cookies[FormsAuthentication.FormsCookieName].Value = null;
}
}
catch (Exception ex)
{
//TODO:log ex
}
}
}
You can also consider to handle the AuthenticateRequest event by declaring the below method in the global.aspx.
NOTE: Application_AuthenticateRequest event is fired after the FormsAuthentication_OnAuthenticate event.
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null && HttpContext.Current.User.Identity.IsAuthenticated)
{
MembershipUser user = Membership.GetUser();
if (user == null)
{
FormsAuthentication.SignOut();
HttpContext.Current.User = null;
}
}
}
Hope this helps..
You create a base controller that overrides the OnActionExecuting and place your code there.
Have all your controller controllers inherit from him. This the OnActionExecuting from a older project I had
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Request.IsAuthenticated)
{
if (Session[SomeKey] == null)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Account" }, { "action", "LogOff" } });
}
}
base.OnActionExecuting(filterContext);
}
I'm basically checking if the request is authenticated otherwise I redirect it to logout action.
http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.onactionexecuting.aspx
I've a Web App that just recently has began randomly losing sessions. The exact cause is elusive at best, however it seems the session is killed/lost on the server side and results in the user needing to close their browser entirely and relaunch in order to log back in.
I wish I could provide some code, but I can't figure out where the problem is at all.
Here is a session action filter we use currently:
public class SessionExpireAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext lvContext = HttpContext.Current;
//if(
// check if session is supported
if (lvContext.Session != null)
{
// check if a new session id was generated
if (lvContext.Session.IsNewSession)
{
// If it says it is a new session, but an existing cookie exists, then it must
// have timed out
string sessionCookie = lvContext.Request.Headers["Cookie"];
if ((null != sessionCookie) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0))
{
lvContext.Response.Redirect("~/Account/Timeout");
}
}
}
base.OnActionExecuting(filterContext);
}
}
Did you add a new feature that adds or removes files from the root directory or any of its subdirectories? That can cause the session to reset.
Ultimately I moved to SQL State Server to handle my sessions. This outsources session handling to the SQL server allowing a session to persist through a recycle, etc. For more information see these links:
Session-State Modes
HOW TO: Configure SQL Server to Store
ASP.NET Session State