3

I'm building a website with ASP.NET MVC3. When a user signs in, I pull their display name from the database and store it to a session variable:

Session["DisplayName"] = user.Display;

Then in _Layout.cshtml I use it to display at the top of each page:

<span class="user">@(Session["DisplayName"] as string)</span>

This works fine when I start debugging the website then log in, but if I then rebuild my server and begin debugging again, my browser remains logged in but the Session variables are cleared. This leads to a bunch of empty spaces where my display name should be.

Is this a side-effect of rebuilding the server, and not something I need to worry about in deployment? Is there a way to invalidate logins every time I rebuild, so that I avoid this issue? Or is there a better way to store this user data, other than a Session variable?

dlras2
  • 8,416
  • 7
  • 51
  • 90

3 Answers3

4

Is this a side-effect of rebuilding the server, and not something I need to worry about in deployment?

Oh no, that's something that you absolutely should worry about. As you know ASP.NET session is by default stored in server memory. And when the AppDomain is recycled (which could happen at absolutely any time) by IIS all your session is gone. For example IIS could bring down the AppDomain after a certain inactivity on the application. Or after certain CPU or memory usage thresholds are reached. If you want to be able to reliable store something in the ASP.NET session you could offload the storage off-proc and store this session either in a dedicated session server or SQL. But honestly, I have seen many people moving ASP.NET Session to SQL Server and regretting it. What's the point? You already have this information in SQL Server :-) I once used this and regretted it so much.

Personally I never use ASP.NET Session in my applications. If you need to persist such information as the display name and avoid hitting the database at each request you could store it in the User Data section of the Forms Authentication cookie. So here's the idea: when the user successfully inputs correct credentials, you manually create a FormsAuthenticationTicket and populate its UserData property with whatever information you want to be available about this use on each request and then emit the authentication cookie. Then you write a custom Authorize attribute in which you decrypt the cookie, fetch the User Data and store it as a custom IIdentity making it available on each request.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • So where should I store my user's display name then? Or does every page need to check for its existence then reload from the database if it's gone? – dlras2 Jun 18 '12 at 20:11
  • Why do you need to be storing it anywhere? It's already store in your database. But if you are concerned about performance and want to avoid hitting the database at each request you could store it in the Forms Authentication cookie user data section. – Darin Dimitrov Jun 18 '12 at 20:11
  • He probably wants to save roundtrips – Dimitri Jun 18 '12 at 20:12
  • There are many ways to save roundtrips. ASP.NET Session is the worst ever possible approach and will lead to much more problems than you are initially trying to solve. – Darin Dimitrov Jun 18 '12 at 20:15
  • @Darin Can you explain how I could do this through the User Data section of the Forms Authentication cookie? I'm not sure I follow. I use `FormsAuthentication.SetAuthCookie(user.Email, false);` to log a user in, then `User.Identity` to check them again, but I don't see anywhere to add user data. – dlras2 Jun 18 '12 at 20:23
  • Sure, take a look at the following post in which I illustrate how you could store the currently authenticated user roles and make them available on each request without querying the database: http://stackoverflow.com/questions/5476059/handling-multiple-roles-in-mvc-action-based-accessibility/5476355#5476355 You should no longer use `FormsAuthentication.SetAuthCookie` -> you need to manually generate the FormsAuthenticationTicket which allows you to set the UserData part of it before encrypting. – Darin Dimitrov Jun 18 '12 at 20:27
  • @Darin That looks like a great solution! I'll probably either do that or just pull straight from the database (I'm not actually terribly worried about round trips.) – dlras2 Jun 18 '12 at 20:31
  • 2
    @DanRasmussen, that's absolutely the correct approach: fetch from DB and if you notice that this is somehow bottleneck (which is very unlikely) for your application you could always optimize. Remember that the HTTP protocol is stateless and trying to change this nature by using some tricks such as session will simply make things worse. – Darin Dimitrov Jun 18 '12 at 20:33
  • You could sync your forms auth and session timeout to ensure the two exist together. Storing data in formsauth tickets after the poet vulnerability (from a security standpoint in general) worries me. True, a name is fairly innocent (except for potentially XSS attacks injected into a hacked cookie as the name instead) but in general, prefer not to store anything in the cookie. Im not advocating the session, but most apps I've seen don't have performance issues caused by the session (thats note of course to say they can't be caused from the session, I'm aware of locking issues, etc from it) – Adam Tuliper Jun 18 '12 at 20:34
  • @AdamTuliper, this information is stored in the encrypted part of the forms authentication cookie. If a hacker is able to modify this cookie value, believe me: his last concern will be to change the display name of the username => he will directly change the username to `admin` :-) So no worries in this aspect. The encrypted forms authentication cookie is secure. If you question its security, really, there are other things to worry about. – Darin Dimitrov Jun 18 '12 at 20:38
  • 1
    As far as the syncing is concerned, I have already commented on this: yes, you can set the same timeout, except that IIS could recycle the application. So the only way to reliably sync them is to use offproc session which kind a kills the whole purpose of it because you already have the information in the database. – Darin Dimitrov Jun 18 '12 at 20:40
  • While I greatly respect your posts Darin, I can't agree on either point here. First, the POET vulnerability showed an encrypted cookie could be attacked thus reinforcing no details should be stored in a cookie. Granted a name is likely fine, but wouldn't want someone to read this and start storing sensitive data in the cookie, encrypted or not. Secondly, even with a recycle, my post below handles that. You can easily force both to be in sync with each other (outside of setting the same timeout - which doesn't actually work) – Adam Tuliper Jun 18 '12 at 20:43
  • @AdamTuliper, how do you handle the case of web farms with sessions stored in-memory? Web farms are pretty common nowadays where you have multiple web server running your web application in production. How do you sync this information between the servers of your web farm? Because the user could be perfectly fine authenticated on nodeA of the farm, but nodeB has no clue about it because session is stored in memory. As far as POET is concerned, Microsoft already released a patch fixing the vulnerability in the encryption process. – Darin Dimitrov Jun 18 '12 at 20:50
  • This being said I completely agree with you that *sensitive data* should not be stored in the authentication cookie. – Darin Dimitrov Jun 18 '12 at 20:57
  • Session state inproc or out of proc makes no difference. If its a web farm, you are using out of proc sql or memcached to distribute across machines. POET while a patch exists, this generally means little in the security world, it happened once, it can happen again and there are unpatched servers out there. I think we both agree storing a name there is OK my concern is when someone reads this and starts storing everything else there. – Adam Tuliper Jun 19 '12 at 05:27
1

Store usernames in cache (Cache[string.Format("DisplayName_{0}", User.Id)] = User.Username), cookies or move session to SQL Server instead of InProc

I would create a static helper method that gets username by user id. If it finds cached value, it will use that, if not, get value from db, store it in cache and return.

public static string GetUsername(int UserID)
{
    string cacheKey=string.Format("DisplayName_{0}", UserID);
    if (HttpContext.Current.Cache[cacheKey]==null)
    {
        // Retrieve user name from DB
        string Username=Repository.GetUserName(UserID);
        HttpContext.Current.Cache[cacheKey]=Username;
    }

    return HttpContext.Current.Cache[cacheKey].ToString();
} 
Dimitri
  • 6,923
  • 4
  • 35
  • 49
0

There are better ways, but to address your specific issue your auth timeout and session timeout are not the same, you need to handle the case specifically when one will timeout before the other. See my post here:

How can I handle forms authentication timeout exceptions in ASP.NET?

Community
  • 1
  • 1
Adam Tuliper
  • 29,982
  • 4
  • 53
  • 71
  • Yes, but even if you the same timeout for both IIS could simply tear down the AppDomain at anytime killing everything stored into the session. The only reliable way to use session in ASP.NET is to make it off-proc. – Darin Dimitrov Jun 18 '12 at 20:31
  • Yes, but then the user is forced to login, at which point the session variables would be repopulated (assuming the app is developed as such) – Adam Tuliper Jun 18 '12 at 20:44
  • ha both said 'yes but'. Just realized that after sending : ) – Adam Tuliper Jun 18 '12 at 20:45