Setting HttpContext.Current.User

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • bproddu
    New Member
    • Jan 2009
    • 7

    Setting HttpContext.Current.User

    Hi,

    I'm using Forms authentication for my web app. I've a custom Principal class that I would like to set to the HttpContext.Cur rent.User after the Membership.Vali dateUser() passes. I'm able to set the value in the login form, however when FormsAuthentica tion.RedirectFr omLoginPage() redirects the call to my Default.aspx the HttpContext.Cur rent.User has a completely different value.

    Within the ValidateUser() I'm constructing my custom principal object and storing it in HttpContext.Cur rent.Applicatio nInstance.Sessi on. I've tried setting the HttpContext.Cur rent.User in the Application_Pos tAuthenticateRe quest but it fails saying HttpContext.Cur rent.Applicatio nInstance.Sessi on is not a valid property.

    How do I set the HttpContext.Cur rent.User and keep it throughout the application?

    Thank you.

    Bala
  • Frinavale
    Recognized Expert Expert
    • Oct 2006
    • 9749

    #2
    Since you're using a custom Principal class you need to imlement the PostAuthenticat e event in your application's Global.asax

    The PostAuthenticat e event occurs after the FormsAuthentica tionModule has verified the forms authentication cookie, and has created the GenericPrincipa l and FormsIdentity Objects.

    It's here that you can create an instance of your custom Principal Object that wraps the FormsIdentity Object....it is here that you store it in the HttpContext.Use r property: before any of your code is loaded.

    When you implement this method you need to:
    • First check that FormsAuthentica tion.CookiesSup ported is true.
    • You need to check if the FormsAuthentica tion.FormsCooki eName is not null/nothing.
    • You need to create a FormsAuthentica tionTicket based what you retrieve from the FormsAuthentica tion cookie
    • You need to create your Identity Object based on the ticket you created
    • You need to create your Principal Object based on the Identity Object
    • And finally you need to set the HttpContext.Cur rent.User property to your Principal object



    If anything fails along the way then you'll have to handle that accordingly.

    At this point you've set the HttpContext.Cur rent.User to your custom Principal before any of your code is executed. This will make it available to all of your code.


    <edit>After telling you all of this, and re-reading your question, I gather you're already doing this.

    I think you're problem is that you are attempting to retrieve information about your Principal from the Session.

    Session is not created/available at this point in the ASP.NET life cycle. That is why the AuthenticationC ookie is used.

    You need to change your application to use cookies instead of Session.

    </edit>
    -Frinny

    Comment

    • bproddu
      New Member
      • Jan 2009
      • 7

      #3
      Thank you for your response. I don't want the WebApplication create my custom principal object. I rather have it call my custom MembershipProvi der and within the ValidateUser() I construct my customPrincipal object and make it available to the WebApp through some mechanism. I want my customPrincipal to be read-only to the callers. I have a SecurityManager class that essentially does all the grunt work of validating users, creating customIdentity/customPrincipal objects, check authorization etc.

      When the Membership.Vali dateUser() is called I'm storing my customPrincipal object in HttpContext.Cur rent.Applicatio nInstance.Sessi on. On the LoginForm.aspx I'm able to retrieve it just fine. Upon redirect to the Default.aspx (and the call goes through Global.asax again), the HttpContext.Cur rent.User is reporting a different principal. I don't want to constantly go to the database and fill in my customPrincipal object for every single call.

      Inside the Login1_Authenti cate() on the LoginForm.aspx:

      if (Membership.Val idateUser(Login 1.UserName, Login1.Password ))
      {
      // get the customPrincipal and assign to the current User
      HttpContext.Cur rent.User = MySecurityManag er.GetPrincipal ();

      // redirect to Default.aspx
      FormsAuthentica tion.RedirectFr omLoginPage(Log in1.UserName, false);
      }

      Inside the MySecurityManag er.ValidateUser (userName, pwd):

      // checks to see if userName and pwd are valid
      ....

      if (isUserValid)
      HttpContext.Cur rent.Applicatio nInstance.Sessi on.Add("myprinc ipal", new MyPrincipal(use rName));

      // NOTE: MyPrincipal queries the database for user and role information

      Inside the MySecurityManag er.GetPrincipal ():

      HttpSessionStat e currSession = HttpContext.Cur rent.Applicatio nInstance.Sessi on;

      if (currSession["staywell.swpri ncipal"] != null)
      return (MyPrincipal)cu rrSession["myprincipa l"];

      NOTE: I have NO code in the Global.asax for now.

      Inside the Default.aspx:

      MySecurityManag er.MyPrincipal p = (MySecurityMana ger.MyPrincipal )HttpContext.Cu rrent.User;

      The above line fails. HttpContext.Cur rent.User is of type RolePrincipal instead of MyPrincipal.

      I went back to the Global.asax, and added the following code to Application_Pos tAuthenticate() :

      HttpContext.Cur rent.User = MySecurityManag er.GetPrincipal ();

      And the above line fails because it can't get to the Session from this event.

      How do I stick my customPrincipal in the session and make it available to all the pages?

      Thanks again.

      Comment

      • Frinavale
        Recognized Expert Expert
        • Oct 2006
        • 9749

        #4
        Originally posted by bproddu;
        I don't want the WebApplication create my custom principal object. I rather have it call my custom MembershipProvi der and within the ValidateUser() I construct my customPrincipal object and make it available to the WebApp through some mechanism.
        ............... ............... ....


        Originally posted by bproddu;
        On the LoginForm.aspx I'm able to retrieve it just fine. Upon redirect to the Default.aspx (and the call goes through Global.asax again), the HttpContext.Cur rent.User is reporting a different principal.
        The reason it's different is because a generic Principal is created to allow your asp application to work and the HttpContext.Cur rent.User is set to this generic Principal Object.

        Originally posted by bproddu;
        Inside the MySecurityManag er.ValidateUser (userName, pwd):

        // checks to see if userName and pwd are valid
        ....

        if (isUserValid)
        HttpContext.Cur rent.Applicatio nInstance.Sessi on.Add("myprinc ipal", new MyPrincipal(use rName));

        // NOTE: MyPrincipal queries the database for user and role information
        You should not be storing your Principal in Session because you will not be able access it in Global.asax in order to recreate the Principal for use in your application.

        I think that you are getting 2 different forms of authentication confused.
        In the past user information was stored in Session and your application would access this information to determine if the user has been authenticated and determine what the user is authorized to use.

        Now Forms Authentication has changed and it lets you create a Principal Object to identify the user. It creates this Principal Object before any code is loaded for the website......so that it can determine what resources the person is authorized to access before the resource is loaded.

        This is a lot more flexible than the old method because the ASP.NET technology will deny access to anything the user is not authorized without relying on your page code to determine this. This can be now applied to all types resources before your code is even considered.

        This means that storing user information in Session won't work because your application hasn't been loaded yet....so Session hasn't been created yet....

        This is why authentication cookies are used to identify the user: because cookies are available at this point in the ASP life cycle.

        So when you ask:

        Originally posted by bproddu;
        I don't want to constantly go to the database and fill in my customPrincipal object for every single call.
        I have to ask you: how else are you going to retrieve information about the user if you don't retrieve the information from the database?



        So, in your Login Page, you should be creating an instance of your Principal Object and using it to authenticate the user. Once the user is authenticated you should then create a FormsAuthentica tionTicket, encrypt it and store it in a cookie so that the user can be identified by ASP.NET upon redirect.

        For example:
        Code:
         Dim testPrincipal As New MyPrincipal(Login1.UserName, Login1.Password)
                If (Not testPrincipal.Identity.IsAuthenticated) Then
                    ' The user is still not validated.
                    Login1.FailureText = "sorry didn't authenticate"
                Else
                    'Create a ticket that will identify the user upon redirect
                    Dim ticket As New FormsAuthenticationTicket(0, Login1.UserName, Date.Now, Date.Now.AddMinutes(20), False, "addional information")
                    'Encrypting the ticket
                    Dim enTicket As String = FormsAuthentication.Encrypt(ticket)
                   'Adding the ticket to a cookie that can be retrieved by the Global.asax 
                    Response.Cookies.Add(New HttpCookie(FormsAuthentication.FormsCookieName, enTicket))
        
                   
                    Response.Redirect(FormsAuthentication.DefaultUrl)
                End If



        Originally posted by bproddu;
        Inside the Default.aspx:

        MySecurityManag er.MyPrincipal p = (MySecurityMana ger.MyPrincipal )HttpContext.Cu rrent.User;

        The above line fails. HttpContext.Cur rent.User is of type RolePrincipal instead of MyPrincipal.
        The reason this fails is because you did not set the HttpContext.Cur rent.User in your Global.asax, this means that the Principal that is stored in there is a Generic Principal...not your custom Principal.

        Originally posted by bproddu;
        I went back to the Global.asax, and added the following code to Application_Pos tAuthenticate() :

        HttpContext.Cur rent.User = MySecurityManag er.GetPrincipal ();

        And the above line fails because it can't get to the Session from this event.
        Your SecurityManager class is accessing Session....Agai n, Session is not available because it has not been created at this point in the ASP page life cycle.

        Originally posted by bproddu;
        How do I stick my customPrincipal in the session and make it available to all the pages?
        You don't. It should be created and stored in HttpContext.Cur rent.User before your code is loaded so that every page and component has access to it.

        -Frinny
        Last edited by Frinavale; Sep 9 '10, 01:08 PM.

        Comment

        • bproddu
          New Member
          • Jan 2009
          • 7

          #5
          I actually started out setting my LoginForm.aspx to create a FormsAuthentica tion ticket, add roles to the ticket using the UserData property, encrypt and send an HttpCookie in the response. However in the Global.asax when I decrypted the ticket and tried to read the UserData property it showed empty.

          Here's that code that didn't work.

          Inside the LoginForm.aspx:

          Code:
          if (Membership.ValidateUser(Login1.UserName, Login1.Password)
          {
            string[] roles = Roles.GetRolesForUser(Login1.UserName);
            string rolesString = String.Join(";", roles);
          
            FormsAuthenticationTicket tkt = new FormsAuthenticationTicket(1, Login1.UserName, DateTime.Now, DateTime.Now.AddMinutes(30), false, rolesString);
          
            string encryptedTkt = FormsAuthentication.Encrypy(tkt);
            HttpCookie formsCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTkt);
          
           Response.Cookies.Add(formsCookie);
          
           FormsAuthentication.RedirectFromLogin(Login1.UserName, false);
          }
          Inside the Application_Pos tAuthenticateRe quest in Global.asax

          Code:
          string cookieName = FormsAuthentication.FormsCookieName;
          HttpCookie formsCookie = Context.Request.Cookies[cookieName];
          
          if (formsCookie == null)
            return;
          
          FormsAuthenticationTicket tkt = null;
          
          try {
             tkt = FormsAuthentication.Decrypt(formsCookie.Value);
          }
          catch (Exception ex){
          }
          
          if (tkt == null)
             return;
          
          string[] roles = tkt.UserData.Split(';');
          The UserData in the above line came back EMPTY

          // not sure how to create my custom Identity and Principal objects here
          Code:
          System.Security.Principal.GenericIdentity identity = new System.Security.Principal.GenericIdentity(tkt.Name, "Generic");
          System.Security.Principal.GenericPrincipal principal = new System.Security.Principal.GenericPrincipal(identity, roles);
          Context.User = principal;
          //System.Threading.Thread.CurrentPrincipal = principal;
          My customIdentity contains UserId, FullName, Telephone, Company etc. fields. I can't simply take a userName field and construct my customIdentity without verifying who the user is. Perhaps Global.asax is not the right place to set HttpContext.Cur rent.User to my customPrincipal ?

          To your question about 'why use session for the principal object':
          I want to construct my customPrincipal object once by querying the database and then storing it either in the session or HttpContext.Cur rent.User for subsequent requests for performance sake.
          Last edited by Frinavale; Sep 9 '10, 01:28 PM. Reason: Please post code in [code] ... [/code] tags. Added code tags.

          Comment

          • liawcv
            New Member
            • Jan 2009
            • 33

            #6
            FormsAuthentica tion.RedirectFr omLoginPage() method will create a new authentication ticket and set it to the response cookies collection. Which mean, your custom authentication ticket has been replaced, when the method is called. DO NOT call this method if you want to use custom authentication ticket. Use Response.Redire ct( ___ , true) instead.

            By the way, you can get the ReturnUrl by calling the FormsAuthentica tion.GetRedirec tUrl( ___ , ___ ) method.

            Comment

            • bproddu
              New Member
              • Jan 2009
              • 7

              #7
              Aah I didn't know that the RedirectFromLog in creates a new ticket. That explains why I also see a new version in the Global.asax after decrypting it.

              I still wanna know how to create my customPrincipal object in the Global.asax and set it to the HttpContext.Cur rent.User without having to query the database again.

              Here's what I'm thinking and please let me know if this is a bad idea:

              I should expose a Membership.GetP rincipal(userNa me, pwd) in my custom MembershipProvi der class and have the LoginForm.aspx call this method insteaf of Membership.Vali dateUser(userNa me, pwd). The GetPrincipal shall return my custom principal object, which then can be encrypted and sent as a cookie to the browser. Upon Response.Redire ct(), in the Global.asax I should decrypt the ticket, deserialize my custom principal object and set it to HttpContext.Cur rent.User.

              This should work, right?

              Do I also have to set the Thread.CurrentP rincipal? I'm also having to do a custom permission object which requires that the caller is authenticated using my custom principal object. My custom permission object takes in a few other parameters to verify if the caller is privileged to perform certain functions.

              Comment

              • liawcv
                New Member
                • Jan 2009
                • 33

                #8
                You mean store the entire Principal object in the cookie? I think that BinarySerializa tion, SOAPSerializati on or XMLSerializatio n won't be a good idea. The nature of cookie is to store a small piece of data. Or you have your own custom serialization method?

                I agree with what Frinny suggested: create your Identity and Principal objects in Global.asax. Yup, not to retrieve the objects from session, cookie or anywhere else, but to create (or construct) the object instances.

                I was worked on a very simple scenario before. I use neither MembershipProvi der nor RoleProvider for my user accounts and roles are relative simple. But I do have my own custom Identity object with properties such as FirstName, LastName, etc. I stick back to generic Principal. Anyway, it works fine.

                I gain the ideas from the following tutorial:
                Forms Authentication Configuration and Advanced Topics
                Not really map to your case, but at least it discussed some basic ideas on how to encapsulate addition user data in the authentication ticket and retrieve them. Hope it helps.

                I have limited experience with MembershipProvi der and RoleProvider. Hope experts can enlighten us... : )

                Comment

                • bproddu
                  New Member
                  • Jan 2009
                  • 7

                  #9
                  I agree with both you guys, however are you suggesting that I build my customPrincipal in the Global.asax for every single request? How else am I going to get the principal object back from the LoginForm.aspx?

                  The identity and the roles don't change as often, so why query it for every page request? May be I'm missing something else here...

                  Comment

                  • liawcv
                    New Member
                    • Jan 2009
                    • 33

                    #10
                    Not to query it from database on every page request. Extra information such as ROLE, FIRST NAME, LAST NAME, etc are to be stored on the authentication ticket as user data (i.e. store in cookie as encrypted info). For my case, in Global.asax, I instantiate a CustomIdentity (my Identity class) object based on the the user data in the authentication ticket. Something like (for my case):

                    Code:
                    ...
                    FormsIdentity fi = (FormsIdentity)HttpContext.Current.User.Identity;
                    FormsAuthenticationTicket ticket = fi.Ticket;
                    string[] userDataPieces = ticket.UserData.Split('|');
                    string[] roles = userDataPieces[0].Split(',');
                    CustomIdentity ci = new CustomIdentity(ticket);
                    HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(ci, roles);
                    ...
                    The content of my authentication ticket's user data is formatted as:
                    ---> <role>|<first name>|<last name>
                    Example of real data is:
                    ---> staff|Alex|Gobi n
                    And my CustomIdentity doesn't perform any database operation at all. All user data is taken from the authentication ticket / cookie.

                    Later at other pages, I can access to these custom user info by using the following method:

                    Code:
                    ...
                    if (User.Identity.IsAuthenticated)
                    {
                       CustomIdentity ci = (CustomIdentity)User.Identity;
                       lblLoginName.Text = ci.Name;
                       lblFirstName.Text = ci.FirstName;
                       lblLastName.Text = ci.LastName;
                    }
                    ...
                    So, no query to database on every page request. Somehow, the CustomIdentity object is to be instantiated on every page request, as just the Page object is created and destroyed on every page request. Nature of the stateless HTTP protocol...

                    But "my case" is not "your case". Different scenarios, different solutions. Hope that you can share your solution with us after you get your job done... : )

                    Comment

                    • bproddu
                      New Member
                      • Jan 2009
                      • 7

                      #11
                      So this goes back to storing user and role information in the encrypted ticket and sending it to the browser as a cookie, and then reading it back in the Global.asax. I'm afraid that I may go beyond the cookie's capacity as I stick in it the different roles a user belongs. Apparently the max limit is 4kb/cookie. I tried compressing the cookie using GZip which resulted in less a KB for user data with 10 roles. After encrypting using FormsAuthentica tion.Encrypt() the size grew to 3.5KB.

                      This may be going back to the subject of storing user/role data in session.

                      Comment

                      • Frinavale
                        Recognized Expert Expert
                        • Oct 2006
                        • 9749

                        #12
                        Just remember, you can't use Session to create your Principal.

                        You could go back to the old fashioned way of doing things and store all of the user's information into Session instead of using Principals. Then you wouldn't even have to use an authentication cookie/ticket....but you would be missing out on the benefits of using Principals.

                        Just out of curiosity, why are you so against querying a database in order to create your Principal Object?

                        Comment

                        • bproddu
                          New Member
                          • Jan 2009
                          • 7

                          #13
                          Hi Firnavale,

                          I'm not against querying the database but I don't want to query it for every request that comes in. I'm sure you agree too, right? I want to cache the principal after I first create it from the database and would like to set the HttpContext.Cur rent.User from the cache. Cookie is a possibility but how do I avoid going over the cookie's capacity?

                          Comment

                          • Frinavale
                            Recognized Expert Expert
                            • Oct 2006
                            • 9749

                            #14
                            Originally posted by bproddu;
                            Hi Firnavale,

                            I'm not against querying the database but I don't want to query it for every request that comes in. I'm sure you agree too, right? I want to cache the principal after I first create it from the database and would like to set the HttpContext.Cur rent.User from the cache. Cookie is a possibility but how do I avoid going over the cookie's capacity?
                            I agree that it would be nice to cache the Principal...but where would you put it?
                            Session's not available when it's created so you can't store it there. So you would have to put it somewhere else. Where would you put it if not in a database?

                            Cookies are tempting, but they are limited...and not only that but they are susceptible to being captured and hacked. Which could leave you open to attack (just change a couple of roles around and ta-da they have privileges to something they shouldn't)

                            So you definitely want to store this information on the server....but where would you store it if not in a database?
                            Last edited by Frinavale; Sep 9 '10, 01:29 PM.

                            Comment

                            Working...