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);
}
}
}
I've implemented a custom IExceptionFilter to handle an exception some users are experiencing with a third party library our application is consuming. When this particular error state occurs, I need to modify the user's cookies (to clear their session state), but what I am finding is that no cookies seem to make it out of the filter. Not sure what's wrong or even how to debug it.
I've modified the functional bits filter to simplify the intent, but here's the gist of the filter. I've ensured that it is the first filter to run on the controller and tested removing the HandleErrorAttribute filter as well to no avail. After the below code runs, "somecookie" is never set on the client.
public class HandleSessionErrorAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext == null) throw new ArgumentNullException("filterContext");
var exception = filterContext.Exception as HttpException;
if (exception != null && exception.Message.Equals("The session state information is invalid and might be corrupted."))
{
filterContext.HttpContext.Response.Cookies.Add(new HttpCookie("somecookie")
{
Value = DateTime.Now.ToString(),
Expires = DateTime.Now.AddMinutes(5),
});
}
}
}
Okay, figured it out. Two issues were preventing the logic from succeeding:
The HandleErrorAttribute must run before any attribute which modifies the response. Part of the implementation of HandleErrorAttribute is to Clear() the response.
CustomErrors must be On for this to work
The initialization code which worked:
GlobalFilters.Filters.Add(new HandleErrorAttribute() { Order = 0 });
GlobalFilters.Filters.Add(new HandleSessionErrorAttribute() { Order = 1 });
I have a group of Asp.Net applications that all share a common HttpModule that contains some code like this:
public class MyModule : IHttpModule
{
public void Dispose()
{
}
public void Init( HttpApplication context )
{
context.Error += new EventHandler( context_Error );
}
private void context_Error( object sender, EventArgs e )
{
var lastError = HttpContext.Current.Server.GetLastError();
doSomeStuffWithError(lastError);
var response = HttpContext.Current.Response;
var redirect = GetErrorRedirect(lastError);
response.Redirect(redirect, true);
}
}
This works totally fine for all of my applications except for one. In the case of the one that doesn't work correctly, response.Redirect(...) doesn't seem to work. Instead of the redirect I expect, Asp.Net is redirecting to its standard error page. I've checked the configuration of this application and don't see anything wrong or significantly different from the other applications.
While investigating this issue, I modified the error handler with one more line of code as follows:
private void context_Error( object sender, EventArgs e )
{
var lastError = HttpContext.Current.Server.GetLastError();
doSomeStuffWithError(lastError);
var response = HttpContext.Current.Response;
var redirect = GetErrorRedirect(lastError);
//setting a break point here, i've verified that 'redirect' has a value in all cases
response.Redirect(redirect, true);
var wtf = response.RedirectLocation;
//inspecting the value of 'wtf' here shows that it is null for one application, but equal to 'redirect' in all others.
}
When I set a break point on 'wtf' I'm seeing some strange behavior. For applications that work, wtf contains the same value as redirect. However, for my app that isn't working, wtf is null.
Anyone have any ideas on what would cause wtf to be null in this way?
The overload of Response.Redirect you are using will call Response.End and throw a ThreadAbortException. It says so in the documentation. So, the fact that "it works" in other applications is interesting as it should never execute var wtf = response.RedirectLocation; During a debugging session, it is not surprising that it's null, either, as there is likely some reason that it allows to execute that line during debug.
In addition, it will of course execute the default error page if you have the mode set to On or RemoteOnly for the <customErrors> setting in Web.config unless you clear the error before redirecting. This is by design.
If you need to execute additional code after you have already called Response.Redirect, pass false as the second parameter to avoid the Response.End call and clear the error using HttpContext.Current.ClearError().
Based on your example, I would rewrite your HttpModule like so:
public class MyModule : IHttpModule
{
public void Dispose()
{
}
public void Init( HttpApplication context )
{
context.Error += new EventHandler( context_Error );
}
private void context_Error( object sender, EventArgs e )
{
var context = HttpContext.Current;
var lastError = context.Server.GetLastError();
doSomeStuffWithError(lastError);
var response = context.Response;
var redirect = GetErrorRedirect(lastError);
context.ClearError();
// pass true if execution must stop here
response.Redirect(redirect, false);
// do other stuff here if you pass false in redirect
}
}
Best way to abort/cancel action from ActionFilter
I've got this ActionFilter, and it's suppose to end the connection immediately and return a 401 Unauthroized:
public class SignInRequired : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// User is verified, continue executing action
if (Acme.Web.CurrentUser != null)
{
return;
}
// End response with 401 Unauthorized
var response = HttpContext.Current.Response;
response.StatusCode = (int)HttpStatusCode.Unauthorized;
response.End();
// Prevent the action from actually being executed
filterContext.Result = new EmptyResult();
}
}
I learned how you can cancel the action from executing by setting 'context.Result = new EmptyResult()` here, but I'm not sure if this is the best way to flush the response and close the connection.
Setting the response will mean the action doesn't get called.
public override void OnActionExecuting(HttpActionContext actionContext)
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
As other answers have said, though, authentication should be done with an AuthorizeAttribute (Docs for Web.API or for MVC).
On .net core 2.2, 3.0 and 3.1 and .net 5 the below example works fine
public override void OnActionExecuting(ActionExecutingContext context)
{
context.Result = new UnauthorizedObjectResult("user is unauthorized");
}
The answer that #OdeyinkaOlubunmi is correct for Web API or specifically System.Web.Http.Filters.ActionFilterAttribute but it can't be used for System.Web.Mvc.ActionFilterAttribute. AuthorizeAttribute and overriding AuthorizeCore is a good way to go but if you use #Vadim's example for a GlobalFilter you will end up with the following error in a standard configuration:
HTTP Error 404.15 - Not Found The request filtering module is
configured to deny a request where the query string is too long.
This is because the default /Login?ReturnUrl= will keep appending new values until the query string causes an exception.
The way I have solved it for MVC is like this:
public class DebugActionFilter : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext actionContext)
{
actionContext.Result = new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
return;
}
}
You can set the result of filterContext for the Exception page like this:
filterContext.Result = new RedirectResult("~/Error/Unauthorized");
See more details here on section Canceling Filter Execution
You probably want to make it an AuthorizeAttribute. That will set the result to be an UnAuthorizedResult automatically, plus it has the benefit of being run before any other filters. Alternatively you can set the Result to be a new HttpUnauthorizedResult
public class SignInRequiredAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return !Acme.Web.CurrentUser != null;
}
}
using .net core 2.1 the solutions above did not work for me , so i tried this and it worked :-
context.HttpContext.Response.StatusCode = 401;
return;
if there is better solutions for .net core 2.1 i am open for suggestions
I'm looking for a tutorial, blog entry, or some help on the technique behind websites that automatically push users (ie without a postback) when the session expires. Any help is appreciated
Usually, you set the session timeout, and you can additionally add a page header to automatically redirect the current page to a page where you clear the session right before the session timeout.
From http://aspalliance.com/1621_Implementing_a_Session_Timeout_Page_in_ASPNET.2
namespace SessionExpirePage
{
public partial class Secure : System.Web.UI.MasterPage
{
public int SessionLengthMinutes
{
get { return Session.Timeout; }
}
public string SessionExpireDestinationUrl
{
get { return "/SessionExpired.aspx"; }
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
this.PageHead.Controls.Add(new LiteralControl(
String.Format("<meta http-equiv='refresh' content='{0};url={1}'>",
SessionLengthMinutes*60, SessionExpireDestinationUrl)));
}
}
}
The SessionExpireDestinationUrl should link to a page where you clear the session and any other user data.
When the refresh header expires, it will automatically redirect them to that page.
You can't really "push" a client from your website. Your site will respond to requests from the client, but that's really it.
What this means is that you need to write something client-side (Javascript) that will determine when the user has timed out, probably by comparing the current time with the most recent time they have in a site cookie (which you update with the current time each time the user visits a page on your site), and then redirect if the difference is greater than a certain amount.
(I note that some people are advocating just creating a script that will forward the user after a certain amount of time on a page. This will work in the simple case, but if the user has two windows open on the site, and is using one window a lot, and the other window not-so-much, the not-so-much one will suddenly redirect the user to the forwarding page, even though the user has been on the site constantly. Additionally, it's not really in sync with any session keeping you're doing on the server side. On the other hand, it's certainly easier to code, and if that's good enough, then great!)
In the <HEAD> section, use a META refresh tag like this:
<meta http-equiv="refresh" content="0000; URL=target_page.html">
where 0000 is your session timeout in seconds, and target_page.html the address of the page to be redirected to.
Using Custom Page class and Javascript also we can achieve it.
Create a custom pagebase class and write the common functionality codes into this class. Through this class, we can share the common functions to other web pages. In this class we need inherit the System.Web.UI.Page class. Place the below code into Pagebase class
PageBase.cs
namespace AutoRedirect
{
public class PageBase : System.Web.UI.Page
{
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
AutoRedirect();
}
public void AutoRedirect()
{
int int_MilliSecondsTimeOut = (this.Session.Timeout * 60000);
string str_Script = #"
<script type='text/javascript'>
intervalset = window.setInterval('Redirect()'," +
int_MilliSecondsTimeOut.ToString() + #");
function Redirect()
{
window.location.href='/login.aspx';
}
</script>";
ClientScript.RegisterClientScriptBlock(this.GetType(), "Redirect", str_Script);
}
}
}
Above AutoRedirect function will be used to redirect the login page when session expires, by using javascript window.setInterval, This window.setInterval executes a javascript function repeatedly with specific time delay. Here we are configuring the time delay as session timeout value. Once it’s reached the session expiration time then automatically executes the Redirect function and control transfer to login page.
OriginalPage.aspx.cs
namespace appStore
{
public partial class OriginalPage: Basepage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
OriginalPage.aspx
<%# Page Language="C#" AutoEventWireup="true" CodeFile="OriginalPage.aspx.cs" Inherits="AutoRedirect.OriginalPage" %>
Web.config
<system.web>
<sessionState mode="InProc" timeout="3"></sessionState>
</system.web>
Note:
The advantage of using Javascript is you could show custom message in alert box before location.href which will make perfect sense to user.
In case if you don't want to use Javascript you could choose meta redirection also
public void AutoRedirect()
{
this.Header.Controls.Add(new LiteralControl(
String.Format("<meta http-equiv='refresh' content='{0};url={1}'>",
this.Session.Timeout * 60, "login.aspx")));
}
Just copy and paste this code snippet in your Web.Config file :
<authentication mode="Forms">
<forms loginUrl="~/Login.aspx" slidingExpiration="true" timeout="29" />
</authentication>
<sessionState timeout="30" mode="InProc" cookieless="false" />
You can put this line to your Site.Master :
Response.AppendHeader("Refresh",
Convert.ToString((Session.Timeout * 60)) +
";URL=~/Login.aspx");
I'm using MVC3 ASp.net as beginner, I tried many solution to solve my session problem ( since i'm using Session variable in my code, and after timeout i didn't have session values while i'm keep using it And I just find that my problem was in config file. the timeout between Authentication and sessionState should be so close. so they Killed (empty) at the same time // add timeout 1 and 2 for testing.. it's should be at least 29 and 30
I used others way it's work too :
Starting from :
protected void Session_Start(object src, EventArgs e)
{
if (Context.Session != null)
{
if (Context.Session.IsNewSession)//|| Context.Session.Count==0)
{
string sCookieHeader = Request.Headers["Cookie"];
if ((null != sCookieHeader) && (sCookieHeader.IndexOf("ASP.NET_SessionId") >= 0))
{
//if (Request.IsAuthenticated)
FormsAuthentication.SignOut();
Response.Redirect("/Account/LogOn");
}
}
}
}
protected void Session_End(object sender, EventArgs e)
{
//Code that runs when a session ends.
//Note: The Session_End event is raised only when the sessionstate mode
//is set to InProc in the Web.config file. If session mode is set to StateServer
//or SQLServer, the event is not raised.
Session.Clear();
}
And :
public class SessionExpireFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
// check if session is supported
if (ctx.Session != null)
{
// check if a new session id was generated
if (ctx.Session.IsNewSession)
{
// If it says it is a new session, but an existing cookie exists, then it must
// have timed out
string sessionCookie = ctx.Request.Headers["Cookie"];
if ((null != sessionCookie) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0))
{
ctx.Response.Redirect("~/Home/LogOn");
}
}
}
base.OnActionExecuting(filterContext);
}
}
And even worked with Ajax to solve session issuse:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Session.Count == 0 || Session["CouncilID"] == null)
Response.Redirect("/Account/LogOn");
if (Request.IsAjaxRequest() && (!Request.IsAuthenticated || User == null))
{
filterContext.RequestContext.HttpContext.Response.StatusCode = 401;
}
else
{
base.OnActionExecuting(filterContext);
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeUserAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAjaxRequest())
{//validate http request.
if (!httpContext.Request.IsAuthenticated
|| httpContext.Session["User"] == null)
{
FormsAuthentication.SignOut();
httpContext.Response.Redirect("~/?returnurl=" + httpContext.Request.Url.ToString());
return false;
}
}
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
Data = new
{
// put whatever data you want which will be sent
// to the client
message = "sorry, but you were logged out"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
Unfortunately it can't be done. The session timeout only occurs on the server side and you won't detect this until the user performs some kind of post back action.
However, what you CAN do is to inject some HTML or JavaScript header code that will automatically push the user to a logout page in the same timeframe as your session timeout. This doesn't guarantee a perfect synch, and you may run into issues if your user is doing some time intensive items and you are not resetting the clock.
I typically add this code to my Page_Load events to accomplish this.
' Register Javascript timeout event to redirect to the login page after inactivity
Page.ClientScript.RegisterStartupScript(Me.GetType, "TimeoutScript", _
"setTimeout(""top.location.href = 'Login.aspx'""," & _
ConfigurationManager.AppSettings("SessionTimeoutMilliseconds") & ");", True)
And if you use the following Logon controller, it will send you to the requested URL before logon:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
//return Redirect(returnUrl);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Of course you need to use [Authorize] over controller class or even Action in specific.
[Authorize]
public class MailController : Controller
{
}
Well this gets tricky for AJAX requests as Zhaph - Ben Duguid pointed out. Here was my solution to make this work with AJAX (using Telerik web controls but they are built using ASP.NET AJAX toolkit I believe).
In a nutshell, I rolled my own sliding expiration session type thing.
In my Site.Master, I am updating a session variable on EVERY postback (postback or AJAX request because AJAX requests still kick off the Page_Load event):
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
if (this.Request.IsAuthenticated)
this.pnlSessionKeepAlive.Visible = true;
else
this.pnlSessionKeepAlive.Visible = false;
}
if (this.Session["SessionStartDateTime"] != null)
this.Session["SessionStartDateTime"] = DateTime.Now;
else
this.Session.Add("SessionStartDateTime", DateTime.Now);
}
Then in my markup for my site.master, I included an iframe with a ASPX page I use "behind the scenes" to check and see if my custom sliding expiration has expired:
<asp:Panel runat="server" ID="pnlSessionKeepAlive" Visible="false">
<iframe id="frame1" runat="server" src="../SessionExpire.aspx" frameborder="0" width="0" height="0" / >
</asp:Panel>
Now in my SessionExpire.aspx page, I just refresh the page every so often and check if the timestamp has lapsed and if so, I redirect to my logout.aspx page that then determines which login page to send the user back to:
public partial class SessionExpire : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
/* We have to do all of this because we need to redirect to 2 different login pages. The default .NET
* implementation does not allow us to specify which page to redirect expired sessions, its a fixed value.
*/
if (this.Session["SessionStartDateTime"] != null)
{
DateTime StartTime = new DateTime();
bool IsValid = DateTime.TryParse(this.Session["SessionStartDateTime"].ToString(), out StartTime);
if (IsValid)
{
int MaxSessionTimeout = Convert.ToInt32(ConfigurationManager.AppSettings["SessionKeepAliveMins"]);
IsValid = (DateTime.Now.Subtract(StartTime).TotalMinutes < MaxSessionTimeout);
}
// either their session expired or their sliding session timeout has expired. Now log them out and redirect to the correct
// login page.
if (!IsValid)
this.Logout();
}
else
this.Logout();
// check every 60 seconds to see if the session has expired yet.
Response.AddHeader("Refresh", Convert.ToString(60));
}
private void Logout()
{
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "TimeoutScript",
"setTimeout(\"top.location.href = '../Public/Logout.aspx'\",\"1000\");", true);
}
}
Many thanks to the people above who posted info, this lead me to my solution and hope it helps others.