I'm using the following code to detect session expiry:
public class SessionActionFilterAttribute : ActionFilterAttribute
{
/// <summary>Called by the ASP.NET MVC framework before the action method executes.</summary>
/// <param name="filterContext">The filter context.</param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// The following code is used for checking if a session has timed out. The default timeout value for ASP.NET is 20mins.
// The timeout value can be overriden in the Web.config file using the sessionState tag's timeout attribute.
// <sessionState timeout="5"></sessionState>
// Check for an existing session.
if (null != filterContext.HttpContext.Session)
{
// Check if we have a new session.
// IsNewSession cannot discern between: is it a new visitor with fresh session, or an existing visitor with expired session.
if (filterContext.HttpContext.Session.IsNewSession)
{
string cookieHeaders = filterContext.HttpContext.Request.Headers["Cookie"];
// Check if session has timed out.
// Does session cookie exist, if so ASP.NET session is expired
if ((null != cookieHeaders) && (cookieHeaders.IndexOf("ASP.NET_SessionId") >= 0))
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
FormsAuthentication.SignOut();
}
// Redirect to login.
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Account" },
{ "action", "Index" },
{ "timeout", "True"}
});
return;
}
}
}
// Else continue with action as usual.
// Session is not expired and function will return false, could be new session, or existing active session
base.OnActionExecuting(filterContext);
}
}
Which works fine up to a point...
When the user is logged in and closes the browser before the session times out (without logging out)...
and then attempts to view the site again and to log back in after the session has timed out it is continually redirecting to the login page, i.e. the above code thinks that the session has expired continuously, but I'm guessing that for some reason the cookie remains as 'expired'.
Is there something I'm missing here?
P.S. I'm using the following in the web.config
<sessionState timeout="1"></sessionState>
Gah.... I added the following just before the redirect and it seems to have fixed the issue.... just a bit more testing to be sure:
if (filterContext.HttpContext.Request.Cookies["ASP.NET_SessionId"] != null)
{
filterContext.HttpContext.Response.Cookies["ASP.NET_SessionId"].Expires = DateTime.Now.AddDays(-1);
}
filterContext.HttpContext.Session.Abandon();
Related
So I have two custom authorize attributes: 1) is to redirect the user to login whenever a session has expired or not authenticated; 2) is currently in progress.
The idea for the second custom authorize attribute is to redirect the user to the same page before he/she navigated to the next page or prevent from redirecting to the next page request. Let say the code is
public class CustomAuth2Attribute : AuthorizeAttribute
{
private const string _errorController = "Error";
public override void OnAuthorization(AuthorizationContext filterContext)
{
var controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var action = filterContext.ActionDescriptor.ActionName;
var area = "";
if (filterContext.RouteData.DataTokens.ContainsKey("area"))
area = filterContext.RouteData.DataTokens["area"].ToString();
if (controller == _errorController)
{
return;
}
// checking the user identity whether the user is allowed to access this page
// then redirect to the previous page before this request and add flash note: "not allowed to access the content"
}
}
The idea is if the user do not have access to a certain page I do not flag this as not authorize instead I should be returning them to the page they were before with the note message.
Also tried the below code:
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller,
action,
area
}));
I'm getting too many redirects which is because I'm referencing the current controller, action, and area instead of the previous one. I also tried getting the UrlReferrer value but this is always null.
Any way I can achieve this? Any help is appreciated. Thank you in advance.
You can override HandleUnauthorizedResult for that:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.UrlReferrer.ToString());
}
I have overridden the HandleUnauthorizedRequest method in my asp.net mvc application to ensure it sends a 401 response to unauthorized ajax calls instead of redirecting to login page. This works perfectly fine when I run it locally, but my overridden method doesn't get called once I deploy to IIS. The debug point doesn't hit my method at all and straight away gets redirected to the login page.
This is my code:
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
filterContext.Result = new JsonResult
{
Data = new
{
success = false,
resultMessage = "Errors"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.HttpContext.Response.End();
base.HandleUnauthorizedRequest(filterContext);
}
else
{
var url = HttpContext.Current.Request.Url.AbsoluteUri;
url = HttpUtility.UrlEncode(url);
filterContext.Result = new RedirectResult(ConfigurationManager.AppSettings["LoginUrl"] + "?ReturnUrl=" + url);
}
}
}
and I have the attribute [AjaxAuthorize] declared on top of my controller. What could be different once it's deployed to IIS?
Update:
Here's how I'm testing, it's very simple, doesn't even matter whether it's an ajax request or a simple page refresh after the login session has expired -
I deploy the site onto my local IIS
Login to the website, go to the home page - "/Home"
Right click on the "Logout" link, "Open in a new tab" - This ensures that the home page is still open on the current tab while
the session is logged out.
Refresh Home page. Now here, the debug point should hit my overridden HandleUnauthorizedRequest method and go through the
if/else condition and then redirect me to login page. But it
doesn't! it just simply redirects to login page straight away. I'm
thinking it's not even considering my custom authorize attribute.
When I run the site from visual studio however, everything works fine, the control enters the debug point in my overridden method and goes through the if/else condition.
When you deploy your web site to IIS, it will run under IIS integrated mode by default. This is usually the best option. But it also means that the HTTP request/response model isn't completely initialized during the authorization check. I suspect this is causing IsAjaxRequest() to always return false when your application is hosted on IIS.
Also, the default HandleUnauthorizedRequest implementation looks like this:
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
filterContext.Result = new HttpUnauthorizedResult();
}
Effectively, by calling base.HandleUnauthorizedRequest(context) you are overwriting the JsonResult instance that you are setting with the default HttpUnauthorizedResult instance.
There is a reason why these are called filters. They are meant for filtering requests that go into a piece of logic, not for actually executing that piece of logic. The handler (ActionResult derived class) is supposed to do the work.
To accomplish this, you need to build a separate handler so the logic that the filter executes waits until after HttpContext is fully initialized.
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new AjaxHandler();
}
}
public class AjaxHandler : JsonResult
{
public override void ExecuteResult(ControllerContext context)
{
var httpContext = context.HttpContext;
var request = httpContext.Request;
var response = httpContext.Response;
if (request.IsAjaxRequest())
{
response.StatusCode = (int)HttpStatusCode.Unauthorized;
this.Data = new
{
success = false,
resultMessage = "Errors"
};
this.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
base.ExecuteResult(context);
}
else
{
var url = request.Url.AbsoluteUri;
url = HttpUtility.UrlEncode(url);
url = ConfigurationManager.AppSettings["LoginUrl"] + "?ReturnUrl=" + url;
var redirectResult = new RedirectResult(url);
redirectResult.ExecuteResult(context);
}
}
}
NOTE: The above code is untested. But this should get you moving in the right direction.
when a user login I store his id in session let say in Session["id"]. on mostly pages I store id from session in an integer and use it in various methods. I put a check on page_load event
protected void Page_Load(object sender, EventArgs e)
{
if (Session["id"] == null)
{
Response.Redirect("Home.aspx");
}
//code goes here
}
What I know is that session expire after 20 min if no request is send to server. but even continuously sending request session expire and i redirected on home page. Is this correct approach or I should try other alternative. any help will be appreciated.
The correct way would be to use the membership API which handles all these details transparently. As shown in this explanatory page you could directly set the timeout interval in membership API using a parameter in the web.config.
Hope I helped!
If there is gap more than 20 minutes between two requests to server then only your session will get expired
Use Permanent User Login Session In ASP.NET thi sample describes how to create a permanent user login session in ASP.NET. The sample code includes an ASP.NET MVC4 project to control the user registration and login process. But you can use this technique in any type of ASP.NET project. But briefly you can use this code
The functionality of this class is to add a form authentication ticket to the browser cookie collection with a life time expiry.
public sealed class CookieHelper
{
private HttpRequestBase _request;
private HttpResponseBase _response;
public CookieHelper(HttpRequestBase request,
HttpResponseBase response)
{
_request = request;
_response = response;
}
//[DebuggerStepThrough()]
public void SetLoginCookie(string userName,string password,bool isPermanentCookie)
{
if (_response != null)
{
if (isPermanentCookie)
{
FormsAuthenticationTicket userAuthTicket =
new FormsAuthenticationTicket(1, userName, DateTime.Now,
DateTime.MaxValue, true, password, FormsAuthentication.FormsCookiePath);
string encUserAuthTicket = FormsAuthentication.Encrypt(userAuthTicket);
HttpCookie userAuthCookie = new HttpCookie
(FormsAuthentication.FormsCookieName, encUserAuthTicket);
if (userAuthTicket.IsPersistent) userAuthCookie.Expires =
userAuthTicket.Expiration;
userAuthCookie.Path = FormsAuthentication.FormsCookiePath;
_response.Cookies.Add(userAuthCookie);
}
else
{
FormsAuthentication.SetAuthCookie(userName, isPermanentCookie);
}
}
}
}
This function is used in login page or control on the click of login button. In the attached sample project, the following function is written in AccountController class. This function validates the login of the user and then add a permanent form authentication ticket to the browser.
private bool Login(string userName, string password,bool rememberMe)
{
if (Membership.ValidateUser(userName, password))
{
CookieHelper newCookieHelper =
new CookieHelper(HttpContext.Request,HttpContext.Response);
newCookieHelper.SetLoginCookie(userName, password, rememberMe);
return true;
}
else
{
return false;
}
}
Need some help with a Session timeout problem in an ASP.Net web app. Essentially the session expires about 10-15 seconds after login.
Side Note: I use a custom combo of FormsAuthentication and basic security
My Session.IsNewSession gets set to true after 3-4 good postbacks after login.
My Web.Config has the following...
<sessionState mode="InProc" timeout="130" regenerateExpiredSessionId="true" stateNetworkTimeout="120" compressionEnabled="true" cookieless="UseCookies" />
<authentication mode="Forms">
<forms timeout="120" ticketCompatibilityMode="Framework40" enableCrossAppRedirects="true" />
</authentication>
Where I believe the timeout refers to minutes....
I have an MVC 3 application with an ActionFilter registered
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyActionFilterAttribute());
}
Inside the OnActionExecuting I check for a Current Session to prevent access to controller actions which an unauthorized user shouldn't be able to access.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
Player player = SessionHelper.GetCurrentPlayer(filterContext.HttpContext.Session);
if (player == null && ctx != null)
{
player = SessionHelper.GetCurrentPlayer(ctx.Session);
}
if (player == null ||
(player.IsAdministrator == false && player.IsCoach == false && player.IsCommittee == false))
{
if (filterContext.Controller is HomeController || filterContext.Controller is AccountController)
{
base.OnActionExecuting(filterContext);
return;
}
string ctxError = ctx != null ? "Context Exists" : "Context Doesnt Exist";
string sessionError = filterContext.HttpContext.Session != null ? "filterContext.HttpContext.Session Exists" : "filterContext.HttpContext.Session Doesnt Exist";
string requestSessionError = filterContext.RequestContext.HttpContext.Session != null ? "filterContext.RequestContext.HttpContext.Session Exists" : "filterContext.RequestContext.HttpContext.Session Doesnt Exist";
throw new SecurityException(string.Format("Attempt to access {0} by user at {1} - {2} ({3}, {4}, {5})",
filterContext.HttpContext.Request.RawUrl,
filterContext.HttpContext.Request.UserHostAddress,
filterContext.HttpContext.Session,
ctxError,
sessionError,
requestSessionError));
}
base.OnActionExecuting(filterContext);
}
So I've determined that the Web Server was refreshing its AppPool faster than my session lifespan. This would result in the same SessionId to be used, but the IsNewSession flag to be set also.
As I have no control over the AppPool lifespan I was able to keep the session in the InProc mode in IIS.
I resolved the issue by moving Session State persistance to a hosted SqlServer database, thereby allowing the session to persist despite the AppPool being recycled.
I'd recommend the solution for any other person seeing their otherwise stable website losing their session state when hosted on a server which they do not have administrative access to.
Oh, and I found that IIS logs were pretty useless here. The best diagnosis logging here, I found, was my own manual logging to determine that this was the scenario.
I have a subscription based MVC 2 application with the basic .NET Membership service in place (underneath some custom components to manage the account/subscription, etc). Users whose accounts have lapsed, or who have manually suspended their accounts, need to be able to get to a single view in the system that manages the status of their account. The controller driving that view is protected using the [Authorize] attribute.
I want to ensure that no other views in the system can be accessed until the user has re-activated their account. In my base controller (from which all my protected controllers derive) I tried modifying the OnActionExecuting method to intercept the action, check for a suspended account, and if it's suspended, redirect to the single view that manages the account status. But this puts me in an infinite loop. When the new action is hit, OnActionExecuting gets called again, and the cycle keeps going.
I don't really want to extend the [Authorize] attribute, but can if need be.
Any other thoughts on how to do this at the controller level?
EDIT: in the base controller, I was managing the redirect (that subsequently created the redirect loop) by modifying the filterContext.Result property, setting it to the RedirectToAction result of my view in question. I noticed everytime the loop occurs, filterContext.Result == null. Perhaps I should be checking against a different part of filterContext?
Ok, so here's my solution in case it helps anyone else. There's got to be a more elegant way to do this, and I'm all ears if anyone has a better idea.
In my BaseController.cs:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
ViewData["CurrentUser"] = CurrentUser; // this is a public property in the BaseController
if (CurrentUser != null && CurrentUser.Account.Status != AccountStatus.Active)
{
// if the account is disabled and they are authenticated, we need to allow them
// to get to the account settings screen where they can re-activate, as well as the logoff
// action. Everything else should be disabled.
string[] actionWhiteList = new string[] {
Url.Action("Edit", "AccountSettings", new { id = CurrentUser.Account.Id, section = "billing" }),
Url.Action("Logoff", "Account")
};
var allowAccess = false;
foreach (string url in actionWhiteList)
{
// compare each of the whitelisted paths to the raw url from the Request context.
if (url == filterContext.HttpContext.Request.RawUrl)
{
allowAccess = true;
break;
}
}
if (!allowAccess)
{
filterContext.Result = RedirectToAction("Edit", "AccountSettings", new { id = CurrentUser.Account.Id, section = "billing" });
}
}
base.OnActionExecuting(filterContext);
}