When is Session State ready for use in asp.net - c#

I have an asp.net site, where in i am checking session variable in Global.asax in Application_BeginRequest but it always says object reference not set to an instance of an object, i don't understand this, because i am checking the condition for null before using the value, but still it throws the above error, however when i check it inside the default.aspx Page_Load event it works properly without any issues.
Can anyone tell whats the problem behind this, am i not supposed to use the session variable inside Application_BeginRequest
If yes, then how i will be checking the session value, what i want to achieve is, if user is logged in (If Session["Login"] is not empty means user logged in) and has the rights to access the page, then allow him/her else throw him to the homepage.
Here is what i am doing.
Below function checks for if user is logged in:
public static String LoggedInUser
{
get
{
if (HttpContext.Current.Session["Login"] == null)
return String.Empty;
else
return HttpContext.Current.Session["Login"].ToString();
}
set
{
HttpContext.Current.Session["Login"] = value;
}
}
Below function checks if user has right to access the page:
public static bool IsPageAllowed(String Pagename)
{
bool _isPageAllowed = false;
XmlDocument doc = new XmlDocument();
doc.Load(HttpContext.Current.Server.MapPath("Pagenames.xml"));
if (LoggedInUser != String.Empty)
{
XmlNodeList list = doc.DocumentElement.SelectNodes("/AllPages/Pages[#user='" + GetLoggedInUserRole(Globals.LoggedInUser).ToLower() + "']/Page[contains(text(), '" + Pagename + "')]");
if (list.Count > 0)
_isPageAllowed = true;
}
return _isPageAllowed;
}
And below function is used on Application_BeginRequest to redirect user based on their rights:
if (!Globals.IsPageAllowed(rawUrl.Substring(1, rawUrl.Length - 1)))
{
Response.Redirect("default.aspx");
}

Session state is available during and after appropriately named HttpApplication.PostAcquireRequestState.
Occurs when the request state (for example, session state) that is associated with the current request has been obtained.
Full sequence of events with descriptions is available somewhere on [MSDN](http://msdn.microsoft.com/en-us/library/bb470252.aspx and other sites like ASP.NET Application Life Cycle
Shortened list of events below (many events are omitted, see MSDN link for details):
BeginRequest
AuthenticateRequest
AcquireRequestState
PostAcquireRequestState
ProcessRequest method (or the asynchronous version IHttpAsyncHandler.BeginProcessRequest) of the appropriate IHttpHandler class for the request. For example, if the request is for a page, the current page instance handles the request.
Note that session state is not available till at least AcquireRequestState (where it may be available if SessionStateModule managed to receive that event before your code). There is no way Session will be available during BeginRequest.
Note that there are explicit authentication events that should be used if you need authentication/authorization (also it is not usable for your case as you keep auth information in session state).

Related

MVC Check Session variable, sign out, and redirect globally

Upon logging in, I am saving a Session variable for that user who just logged in. This Session variable is very very important for every single thing the user then see's (see this question MVC Individual User Accounts login value persisting)
I see there being a potential problem where the Session variable is not tied in with the user being logged in, it has its own expiry (unless someone can give a 100% fool proof way of that NOT happening, for example, when I restart debugging, I am still logged in but the session variable has gone, regardless of expiry).
What I do then, is to check the Session variable exists, and sign them out and redirect if its null. However the redirect needs to be done within the Action, and would need to happen for EVERY get request, so there will be a lot of duplicate code.
var customerIdSession = Session["CustomerId"];
if (customerIdSession == null)
{
// Then sign out which needs the AuthenticationManager property to be defined in the controller too!
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return RedirectToAction("Login", "Account");
}
var customerId = Convert.ToInt32(customerIdSession);
Is there a way I could tidy this up? And not have to do this on EVERY get method. Make it a global check somehow, like Authorize does for login
I found the answer on my own in the end. I took the idea I had of it being similar to the Authorize so found a way to make my own Authrorize attribute. This way, I can put the attribute above every Controller then assume the Session variable exists in every action. If not, the user is logged out and redirected automatically.
public class AuthorizeSessionAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var customerId = httpContext.Session["CustomerId"];
if (customerId == null)
{
var lo = httpContext.GetOwinContext().Authentication;
lo.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
return false;
}
return true;
}
}

On handling any event related to login / logout activity

Currently, my application can track whenever user has logged in or out. The logging out activity is saved to database because User simply click a button. However, I would be interested in how to implement something similar but when user's ticket is no longer valid, either because it's been removed or expired.
I would like to handle any event (if possible) that is fired whenever user is no longer valid.
Is it possible to implement anything like this?
Cheers!
EDIT
I found two solutions. One uses BaseController--the code below from Handle asp.net MVC session expiration. This is simply what the author claims the easiest and working solution. First, is it a good idea to inherit all conrollers from a custom controllers? Second, I have already customized my own [Authorize] attribute. I don't know how all this together is going to work, and three, can i still have access to the cookie of a logged user where I hold UserID and some other data such as UserRole? And also it is not triggered automatically but whenver user trying to access and cookie is expired. It could be 6 hours later.
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Check if session exists
if (filterContext.HttpContext.Session != null)
{
//if new session
if (filterContext.HttpContext.Session.IsNewSession)
{
string cookie = filterContext.HttpContext.Request.Headers["Cookie"];
//if cookie exists and session id index is greater than zero
if ((cookie != null) && (cookie.IndexOf("ASP.NET_SessionId") >= 0))
{
//redirect to desired session expiration action and controller
filterContext.Result = RedirectToAction("SessionExpired", "Home");
return;
}
}
}
//otherwise continue with action
base.OnActionExecuting(filterContext);
}
}
The second solution I found is based on Global.aspx's function
void Session_End(object sender, EventArgs e)
{
Response.Redirect("~/Account/Timeout");
}
but I read it only works when I use InProc. I don't know what that is, though.
If you go with the filter [Authorize] you can add it as a global filter.
filters.Add(new AuthorizeAttribute());
Which is what your controller does too.
To answer your questions:
Yes, if you are building a framework that impacts all controllers. There is no problem with inheriting from a base controller.
Looks like the global filter is right up your alley.
You can't access the cookie once the session expires since there is no client!
When a session expires, the screen gets frozen to information they've already been authorized for until any subsequent request is made.
Hope that helps.

How do I handle a request when the request parameter does not exist?

I'm redirecting from one page to another by sending an id via url and then getting it through the "Request". The problem I have is that there are two ways to get to the page.
Going through the link that will send you onwards with an id in the url. (this works)
By directly going to the page thus not bringing any id with you. (this does not and thows the error)
Exception Details: System.InvalidOperationException: Nullable object
must have a value.
How the links will look when you are redirected for scenario 1 and when you go directly in scenario 2.
1. http://localhost/red/time/time.aspx?id=154
2. http://localhost/red/time/
if (!IsPostBack)
{
//Bladdra(4, 0);
txtTidrapportnr.Text = Request["id"];
if (Request != null)
{
Hamta(General.ToInt(txtTidrapportnr.Text).Value);
Visa();
btnNy.Focus();
}
else
{
Bladdra(4, 0);
Visa();
btnNy.Focus();
}
}
My question is how do I get the page to work both in scenario 1 and 2. It seems like
if (Request != null)
is not correct.
You are doing a wrong thing is that befor checking the null value you are trying to assign it with textbox value the proper method to doing things is as follow:
if (!IsPostBack)
{
//Bladdra(4, 0);
//txtTidrapportnr.Text = Request["id"];
if (Request["id"] != null)
{
txtTidrapportnr.Text =Convert.ToString(Request["id"]);
Hamta(General.ToInt(txtTidrapportnr.Text).Value);
Visa();
btnNy.Focus();
}
else
{
Bladdra(4, 0);
Visa();
btnNy.Focus();
}
}
If your page should only work when the id request parameter then you will have to check for its presence yourself as others have suggested - you cannot stop a page from being accessed automatically as there is nothing stopping you from hitting the URL manually from a browser.
If the parameter is not present you should then handle it gracefully. A typical method I use is to redirect to a known safe page (e.g. homepage) if the URL does not contain everything I need, but I suppose you could also display a warning message on the page as well.

asp_sessionid cookie default path to be made application specific

I have a website in which I had to make the default path which is usually visible as "/" for asp.netSessionId cookie and .ASPXAUTH cookie to application specific for security reasons and after lot of RND i was able to do that but I have a confusion and a problem now described as follows:
The confusion is: In order to make the cookie path application specific I wrote a method say "setcookiepath" to do so and called that method from global file's session_start but that did not work that as the path either used to get reset sometime after certain clicks or more than 1 cookie got generated one with default path and other with app specific path. So I went ahead and called my method 1 by 1 from other events in global.asax file and finally ended up calling from each one, still it worked only sometimes. Then I abandoned session in my Session_Start only the first time when Application is called by using a flag that i set in application_start and reset after session abandons in session_start. This time it worked perfectly fine. Now, i tried removing unneccesary calls from setcookiepath and tested this time it is working with single method call in any of the events and even after removing the logic of abandoning session it is working now.I am confused what is the right approach?
The problem is: Although now my code is working for the website but there is an Admin module which is called from a sub folder within that website. For this Admin sub module my code is not working i.e. the problem I am facing is. I am able to login to Admin module the first time I hit the url and 2 cookies get generated for ASP.NetSessionId one with default path and the other has application specific path. This works as long as my browser is open, when I close the browser and try to relogin, now my previous session cookies are removed and I keep getting redirected to login page because the user session gets invalidated in between somewhere when I am redirecting from login page to home page for Admin module, this issue is not occuring in main website module. Also, after removing the path reset logic from global.asax.cs file admin module session works fine between login and home page.
Please help.
Code for path set is :
public void setCookieApp()
{
if (HttpContext.Current != null)
{
/// only apply session cookie persistence to requests requiring session information
if (HttpContext.Current.Handler is IRequiresSessionState || HttpContext.Current.Handler is IReadOnlySessionState)
{
SessionStateSection sessionState = (SessionStateSection)ConfigurationManager.GetSection("system.web/sessionState");
string cookieName = sessionState != null && !string.IsNullOrEmpty(sessionState.CookieName) ? sessionState.CookieName : "ASP.NET_SessionId";
string cookieName1 = sessionState != null && !string.IsNullOrEmpty(sessionState.CookieName) ? sessionState.CookieName : ".ASPXAUTH";
//System.TimeSpan timeout = sessionState != null ? sessionState.Timeout : TimeSpan.FromMinutes(20);
/// Ensure ASP.NET Session Cookies are accessible throughout the subdomains.
if (HttpContext.Current.Request.Cookies[cookieName] != null)
{
//Response.Cookies[cookieName].Value = Session.SessionID;
HttpContext.Current.Response.Cookies[cookieName].Path = HttpContext.Current.Request.ApplicationPath;
//Response.Cookies[cookieName].Expires = DateTime.Now.Add(timeout);
}
if (HttpContext.Current.Request.Cookies[cookieName1] != null)
{
//Response.Cookies[cookieName].Value = Session.SessionID;
HttpContext.Current.Response.Cookies[cookieName1].Path = HttpContext.Current.Request.ApplicationPath;
//Response.Cookies[cookieName].Expires = DateTime.Now.Add(timeout);
}
}
foreach (string k in HttpContext.Current.Request.Cookies.AllKeys)
{
if (k.ToString().Contains("ASP.NET_SessionId"))
{
HttpContext.Current.Response.Cookies["ASP.NET_SessionId"].Path = HttpContext.Current.Request.ApplicationPath;
}
else if (k.ToString().Contains(".ASPXAUTH"))
{
HttpContext.Current.Response.Cookies[".ASPXAUTH"].Path = HttpContext.Current.Request.ApplicationPath;
}
}
}
}

ASP.NET User Authentication across multiple projects

I am building an ASP.NET UI on an existing system, which consists of separate SQL server databases for each project. An "enterprise" database lists all current projects which allows anonymous users to select the project to work in. The project name is stored in a session variable. When log in is required the username/password/roles etc are obtained from the database indicated by the project name. I have implemented my own basic membership and role providers to do this, with changes in web.config to specify the roles required for specific pages. (I do not use the standard ASP.NET Configuration tool to manage users, I have existing apps that work with my user tables).
This all seemed to work initially but I discovered that the session variables are not yet loaded at the time when the authorization system checks the roles the current user belongs to in order to determine if the page is accessible. So if we have a < allow roles="xxx" > in web.config then the authorization system fires before session data is loaded and thus before I know which project database should be used.
[Specifically: HttpContext.Current.Session is null when the call to RoleProvider.GetRolesForUser is made]
Anybody who has tackled this problem should know exactly what I'm talking about. My questions therefore are:
A) What is the "Best Practise" solution to this scenario?
B) Could I be storing the project name somewhere else (not in session variable) that is available during the authorization phase?
[Update: Yes - we can use cookies, assuming we do not require cookieless operation]
C) Is there a way to manually get the session variable at this earlier time?
I tried an option to cache roles in cookies, but after a few minutes of testing with that option on I found GetRolesForUsers was still being called.
Thanks
Update:
Here is another description of the root problem which suggests "The application could cache this information in the Cache or Application objects.":
http://connect.microsoft.com/VisualStudio/feedback/details/104452/session-is-null-in-call-to-getrolesforuser
Update:
This looks like the same problem found here:
Extending the RoleProvider GetRolesForUser()
Update:
There was a suggestion about using UserData in FormsAuthenticationTicket, but I require this data even when not logged on.
UPDATE: I solve this these days in a much simpler way by using a wildcard SSL certificate that allows me to configure subdomains for each project, thus the project selection is specified directly in the URL (and each project gets its own subdomain). I still use a cookie hack purely for testing purposes when running on localhost where we have no subdomains.
Original solution:
I have not found any "best practise" write up on this scenario, but here is what I have settled on:
1) In order to support anonymous users switching between projects (i.e. SQL databases) I simply use a session variable to track the project selection. I have a global property that uses this project selection to serve the corresponding SQL connection string as and when it is required.
2) In order to support the call to GetRolesForUser() on pages that have role restrictions applied to them we cannot use the session variable, because as stated the session variable has not been initialized yet when GetRolesForUser() is actually called (and I have found no way to force it into being at this early point in the request cycle).
3) The only option is to use a cookie, or use the Forms Authentication ticket's UserData field. I trawled through many theories about using session/cookie/IDs linked to an object stored in the application cache (which is available when the session is not) but ultimately the correct choice is to place this data in the authentication ticket.
4) If a user is logged on to a project it is via a ProjectName/UserName pair, hence anywhere we are tracking the user's authentication we require both these data. In trivial testing we can get away with the username in the ticket and the projectname in a separate cookie, however it is possible for these to get out of synch. For example if we use a session cookie for the projectname and tick "remember me" when we logon (creating a permanent cookie for the authentication ticket) then we can end up with a username but no projectname when the session cookie expires (browser is closed). Hence I manually add the project name to the UserData field of the authentication ticket.
5) I have not figured out how to manipulate the UserData field without explicitly setting a cookie, which means that my solution cannot work in "cookieless" session mode.
The final code turned out to be relatively simple.
I override the Authenticate event of the LoginView in the login page:
//
// Add project name as UserData to the authentication ticket.
// This is especially important regarding the "Remembe Me" cookie - when the authentication
// is remembered we need to know the project and user name, otherwise we end up trying to
// use the default project instead of the one the user actually logged on to.
//
// http://msdn.microsoft.com/en-us/library/kybcs83h.aspx
// http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.login.remembermeset(v=vs.100).aspx
// http://www.hanselman.com/blog/AccessingTheASPNETFormsAuthenticationTimeoutValue.aspx
// http://www.csharpaspnetarticles.com/2009/02/formsauthentication-ticket-roles-aspnet.html
// http://www.hanselman.com/blog/HowToGetCookielessFormsAuthenticationToWorkWithSelfissuedFormsAuthenticationTicketsAndCustomUserData.aspx
// http://stackoverflow.com/questions/262636/cant-set-formsauthenicationticket-userdata-in-cookieless-mode
//
protected void LoginUser_Authenticate(object sender, AuthenticateEventArgs e)
{
string userName = LoginUser.UserName;
string password = LoginUser.Password;
bool rememberMe = LoginUser.RememberMeSet;
if ( [ValidateUser(userName, password)] )
{
// Create the Forms Authentication Ticket
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
userName,
DateTime.Now,
DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes),
rememberMe,
[ ProjectName ],
FormsAuthentication.FormsCookiePath);
// Create the encrypted cookie
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
if (rememberMe)
cookie.Expires = DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes);
// Add the cookie to user browser
Response.Cookies.Set(cookie);
// Redirect back to original URL
// Note: the parameters to GetRedirectUrl are ignored/irrelevant
Response.Redirect(FormsAuthentication.GetRedirectUrl(userName, rememberMe));
}
}
I have this global method to return the project name:
/// <summary>
/// SQL Server database name of the currently selected project.
/// This name is merged into the connection string in EventConnectionString.
/// </summary>
public static string ProjectName
{
get
{
String _ProjectName = null;
// See if we have it already
if (HttpContext.Current.Items["ProjectName"] != null)
{
_ProjectName = (String)HttpContext.Current.Items["ProjectName"];
}
// Only have to do this once in each request
if (String.IsNullOrEmpty(_ProjectName))
{
// Do we have it in the authentication ticket?
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = identity.Ticket;
_ProjectName = ticket.UserData;
}
}
}
// Do we have it in the session (user not logged in yet)
if (String.IsNullOrEmpty(_ProjectName))
{
if (HttpContext.Current.Session != null)
{
_ProjectName = (string)HttpContext.Current.Session["ProjectName"];
}
}
// Default to the test project
if (String.IsNullOrEmpty(_ProjectName))
{
_ProjectName = "Test_Project";
}
// Place it in current items so we do not have to figure it out again
HttpContext.Current.Items["ProjectName"] = _ProjectName;
}
return _ProjectName;
}
set
{
HttpContext.Current.Items["ProjectName"] = value;
if (HttpContext.Current.Session != null)
{
HttpContext.Current.Session["ProjectName"] = value;
}
}
}
Can't you postback the project selection to some page, add that selection to the session, then redirect to appropriate protected page, where auth will kick in and force login?
ASP.NET session doesn't get created in the form of a cookie until you place at least one item in it.

Categories

Resources