469,935 Members | 1,730 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 469,935 developers. It's quick & easy.

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.Current.User after the Membership.ValidateUser() passes. I'm able to set the value in the login form, however when FormsAuthentication.RedirectFromLoginPage() redirects the call to my Default.aspx the HttpContext.Current.User has a completely different value.

Within the ValidateUser() I'm constructing my custom principal object and storing it in HttpContext.Current.ApplicationInstance.Session. I've tried setting the HttpContext.Current.User in the Application_PostAuthenticateRequest but it fails saying HttpContext.Current.ApplicationInstance.Session is not a valid property.

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

Thank you.

Bala
Jan 12 '09 #1
13 50375
Frinavale
9,735 Expert Mod 8TB
Since you're using a custom Principal class you need to imlement the PostAuthenticate event in your application's Global.asax

The PostAuthenticate event occurs after the FormsAuthenticationModule has verified the forms authentication cookie, and has created the GenericPrincipal 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.User property: before any of your code is loaded.

When you implement this method you need to:
  • First check that FormsAuthentication.CookiesSupported is true.
  • You need to check if the FormsAuthentication.FormsCookieName is not null/nothing.
  • You need to create a FormsAuthenticationTicket based what you retrieve from the FormsAuthentication 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.Current.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.Current.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 AuthenticationCookie is used.

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

</edit>
-Frinny
Jan 13 '09 #2
Thank you for your response. I don't want the WebApplication create my custom principal object. I rather have it call my custom MembershipProvider 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.ValidateUser() is called I'm storing my customPrincipal object in HttpContext.Current.ApplicationInstance.Session. 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.Current.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_Authenticate() on the LoginForm.aspx:

if (Membership.ValidateUser(Login1.UserName, Login1.Password))
{
// get the customPrincipal and assign to the current User
HttpContext.Current.User = MySecurityManager.GetPrincipal();

// redirect to Default.aspx
FormsAuthentication.RedirectFromLoginPage(Login1.U serName, false);
}

Inside the MySecurityManager.ValidateUser(userName, pwd):

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

if (isUserValid)
HttpContext.Current.ApplicationInstance.Session.Ad d("myprincipal", new MyPrincipal(userName));

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

Inside the MySecurityManager.GetPrincipal():

HttpSessionState currSession = HttpContext.Current.ApplicationInstance.Session;

if (currSession["staywell.swprincipal"] != null)
return (MyPrincipal)currSession["myprincipal"];

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

Inside the Default.aspx:

MySecurityManager.MyPrincipal p = (MySecurityManager.MyPrincipal)HttpContext.Current .User;

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

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

HttpContext.Current.User = MySecurityManager.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.
Jan 13 '09 #3
Frinavale
9,735 Expert Mod 8TB
I don't want the WebApplication create my custom principal object. I rather have it call my custom MembershipProvider and within the ValidateUser() I construct my customPrincipal object and make it available to the WebApp through some mechanism.
..................................


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.Current.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.Current.User is set to this generic Principal Object.

Inside the MySecurityManager.ValidateUser(userName, pwd):

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

if (isUserValid)
HttpContext.Current.ApplicationInstance.Session.Ad d("myprincipal", new MyPrincipal(userName));

// 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:

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 FormsAuthenticationTicket, encrypt it and store it in a cookie so that the user can be identified by ASP.NET upon redirect.

For example:
Expand|Select|Wrap|Line Numbers
  1.  Dim testPrincipal As New MyPrincipal(Login1.UserName, Login1.Password)
  2.         If (Not testPrincipal.Identity.IsAuthenticated) Then
  3.             ' The user is still not validated.
  4.             Login1.FailureText = "sorry didn't authenticate"
  5.         Else
  6.             'Create a ticket that will identify the user upon redirect
  7.             Dim ticket As New FormsAuthenticationTicket(0, Login1.UserName, Date.Now, Date.Now.AddMinutes(20), False, "addional information")
  8.             'Encrypting the ticket
  9.             Dim enTicket As String = FormsAuthentication.Encrypt(ticket)
  10.            'Adding the ticket to a cookie that can be retrieved by the Global.asax 
  11.             Response.Cookies.Add(New HttpCookie(FormsAuthentication.FormsCookieName, enTicket))
  12.  
  13.  
  14.             Response.Redirect(FormsAuthentication.DefaultUrl)
  15.         End If



Inside the Default.aspx:

MySecurityManager.MyPrincipal p = (MySecurityManager.MyPrincipal)HttpContext.Current .User;

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

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

HttpContext.Current.User = MySecurityManager.GetPrincipal();

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

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.Current.User before your code is loaded so that every page and component has access to it.

-Frinny
Jan 13 '09 #4
I actually started out setting my LoginForm.aspx to create a FormsAuthentication 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:

Expand|Select|Wrap|Line Numbers
  1. if (Membership.ValidateUser(Login1.UserName, Login1.Password)
  2. {
  3.   string[] roles = Roles.GetRolesForUser(Login1.UserName);
  4.   string rolesString = String.Join(";", roles);
  5.  
  6.   FormsAuthenticationTicket tkt = new FormsAuthenticationTicket(1, Login1.UserName, DateTime.Now, DateTime.Now.AddMinutes(30), false, rolesString);
  7.  
  8.   string encryptedTkt = FormsAuthentication.Encrypy(tkt);
  9.   HttpCookie formsCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTkt);
  10.  
  11.  Response.Cookies.Add(formsCookie);
  12.  
  13.  FormsAuthentication.RedirectFromLogin(Login1.UserName, false);
  14. }
Inside the Application_PostAuthenticateRequest in Global.asax

Expand|Select|Wrap|Line Numbers
  1. string cookieName = FormsAuthentication.FormsCookieName;
  2. HttpCookie formsCookie = Context.Request.Cookies[cookieName];
  3.  
  4. if (formsCookie == null)
  5.   return;
  6.  
  7. FormsAuthenticationTicket tkt = null;
  8.  
  9. try {
  10.    tkt = FormsAuthentication.Decrypt(formsCookie.Value);
  11. }
  12. catch (Exception ex){
  13. }
  14.  
  15. if (tkt == null)
  16.    return;
  17.  
  18. 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
Expand|Select|Wrap|Line Numbers
  1. System.Security.Principal.GenericIdentity identity = new System.Security.Principal.GenericIdentity(tkt.Name, "Generic");
  2. System.Security.Principal.GenericPrincipal principal = new System.Security.Principal.GenericPrincipal(identity, roles);
  3. Context.User = principal;
  4. //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.Current.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.Current.User for subsequent requests for performance sake.
Jan 13 '09 #5
liawcv
33
FormsAuthentication.RedirectFromLoginPage() 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.Redirect( ___ , true) instead.

By the way, you can get the ReturnUrl by calling the FormsAuthentication.GetRedirectUrl( ___ , ___ ) method.
Jan 13 '09 #6
Aah I didn't know that the RedirectFromLogin 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.Current.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.GetPrincipal(userName, pwd) in my custom MembershipProvider class and have the LoginForm.aspx call this method insteaf of Membership.ValidateUser(userName, pwd). The GetPrincipal shall return my custom principal object, which then can be encrypted and sent as a cookie to the browser. Upon Response.Redirect(), in the Global.asax I should decrypt the ticket, deserialize my custom principal object and set it to HttpContext.Current.User.

This should work, right?

Do I also have to set the Thread.CurrentPrincipal? 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.
Jan 13 '09 #7
liawcv
33
You mean store the entire Principal object in the cookie? I think that BinarySerialization, SOAPSerialization or XMLSerialization 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 MembershipProvider 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 MembershipProvider and RoleProvider. Hope experts can enlighten us... : )
Jan 14 '09 #8
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...
Jan 14 '09 #9
liawcv
33
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):

Expand|Select|Wrap|Line Numbers
  1. ...
  2. FormsIdentity fi = (FormsIdentity)HttpContext.Current.User.Identity;
  3. FormsAuthenticationTicket ticket = fi.Ticket;
  4. string[] userDataPieces = ticket.UserData.Split('|');
  5. string[] roles = userDataPieces[0].Split(',');
  6. CustomIdentity ci = new CustomIdentity(ticket);
  7. HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(ci, roles);
  8. ...
  9.  
The content of my authentication ticket's user data is formatted as:
---> <role>|<first name>|<last name>
Example of real data is:
---> staff|Alex|Gobin
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:

Expand|Select|Wrap|Line Numbers
  1. ...
  2. if (User.Identity.IsAuthenticated)
  3. {
  4.    CustomIdentity ci = (CustomIdentity)User.Identity;
  5.    lblLoginName.Text = ci.Name;
  6.    lblFirstName.Text = ci.FirstName;
  7.    lblLastName.Text = ci.LastName;
  8. }
  9. ...
  10.  
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... : )
Jan 14 '09 #10
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 FormsAuthentication.Encrypt() the size grew to 3.5KB.

This may be going back to the subject of storing user/role data in session.
Jan 14 '09 #11
Frinavale
9,735 Expert Mod 8TB
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?
Jan 15 '09 #12
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.Current.User from the cache. Cookie is a possibility but how do I avoid going over the cookie's capacity?
Jan 15 '09 #13
Frinavale
9,735 Expert Mod 8TB
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.Current.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?
Jan 15 '09 #14

Post your reply

Sign in to post your reply or Sign up for a free account.

Similar topics

3 posts views Thread by John Dalberg | last post: by
reply views Thread by =?Utf-8?B?QW50b25pbyBPJydOZWFs?= | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.