First time here? Check out the site's "greatest hits" or read a post from the archives. Feel free to leave a comment or ask a question, and consider subscribing to the latest posts via RSS or e-mail. Thanks for visiting!
« Betsy says I'm dapper, so it must be so!... | Main | Piping command-line output directly to t... »

Warning: I find this fascinating and amazing as a caused a suble bug and was generally bizarre today.  You likely don't care. :)

I have some code in an ASP.NET custom FormsAuthentication Login that looks something like this:

// This principal will flow throughout the request.
VoyagerPrincipal principal = new VoyagerPrincipal(yada, yada, yada);

// Attach the new principal object to the current HttpContext object
HttpContext.Current.User = principal;

It it called on the Global.asax's AuthenticateRequest so everything is all setup before the Page's events fire.  It provides a custom IPrincipal that integrates our eFinance Server with ASP.NET.  It's quite a lovely subsystem, IMHO.

Other operations count on being able to get this 'Call Context' IPrincipal from the current thread at any time.  In another section of code someone was doing this in the MIDDLE of the HttpRequest (somewhere in the Page_Load) after having JUST called the routine above for the first time:

return Thread.CurrentPrincipal as VoyagerPrincipal;

Assuming, of course that the Thread's CurrentPrincipal is that same Principal.  And 99.999% percent of the time it is, except when it isn't at all.

In the instance where someone calls the first chunk of code then expects to be able to call the second chunk within the same HttpRequest, the Thread.CurrentPrincipal contains a GenericPrincipal populated much earlier by the HttpApplication.  (Or a WindowsPrincipal, depending on your settings).

  • When the first chunk of code runs in the Global.asax's AuthenticateRequest these two properties ARE in fact the same object
  • When the first chunk of code runs in the context of a Page (read: later!) these properties are NOT the same object.

Why? Reflector tells us in the HttpApplication's internal OnThreadEnter:

internal void OnThreadEnter()
{
      this._savedContext = HttpContextWrapper.SwitchContext(this._context);
      this._context.Impersonation.Start(false, true);
      HttpRuntime.RequestTimeoutManager.Add(this._context);
      this.SetPrincipalOnThread(this._context.User);
      this.SetCulture(false);
}

internal void SetPrincipalOnThread(IPrincipal principal)
{
      if (!this._restorePrincipal)
      {
            this._restorePrincipal = true;
            this._savedPrincipal = Thread.CurrentPrincipal;
      }
      Thread.CurrentPrincipal = principal;
}

I had assumed, wrongly, that these two objects were coming from the same object reference always.  In fact, they are early on, but you can (as I did) change one without changing the other.  So, the first chunk of code becomes this:

// This principal will flow throughout the request.
VoyagerPrincipal principal = new VoyagerPrincipal(yada, yada, yada);

// Attach the new principal object to the current HttpContext object
HttpContext.Current.User = principal;

// Make sure the Principal's are in sync
System.Threading.Thread.CurrentPrincipal = System.Web.HttpContext.Current.User;

And all is right with my world, and the folks can continue to get the expected behavior when doing a "mid-page" FormAuthentication login.

Tracked by:
"Avoid using Impersonation in ASP.NET" (Scott Hanselman's Computer Zen) [Trackback]
"Avoid using Impersonation in ASP.NET" (Ajax.NET Professional - AJAX and JSON ma... [Trackback]


Thursday, September 09, 2004 12:46:23 AM (Pacific Standard Time, UTC-08:00)
Hi!

You just found the famous TLS bug :)
http://www.hanselman.com/blog/PermaLink.aspx?guid=320
Some information can be found in that link too.
The main point: You should NEVER do this:
System.Threading.Thread.CurrentPrincipal = System.Web.HttpContext.Current.User;

Peter
Thursday, September 09, 2004 5:49:36 AM (Pacific Standard Time, UTC-08:00)
Hey Scott,

Another caveat.

You said "When the first chunk of code runs in the Global.asax's AuthenticateRequest these two properties ARE in fact the same object". This isn't entirely true.

When you set the HttpContext.Current.User to your custom IPrincipal in the Global.asax AuthenticateRequest, the Thread.CurrentPrincipal is still the Generic/Windows principal set in the HttpApplication.OnThreadEnter. So if any other method tries to pull out your custom IPrincipal *during* the AuthenticateRequest, they will be in for a suprise. (We hit this exact scenario ourselves). I'll have to do a little more research, but basically sometime after the Global.asax AuthenticateRequest, but before the Page is called, ASP.NET sets the Thread.CurrentPrincipal to the same principal as HttpContext.Current.User. Meaning that your statement is true sometime *after* AuthenticateRequest.

Lesson: Even in Global.asax AuthenticateRequest, set both HttpContext.Current.User and Thread.CurrentPrincipal to your custom IPrincipal.

JasonL

Peter: could you clarify/point to additional info?
Thursday, September 09, 2004 7:47:47 AM (Pacific Standard Time, UTC-08:00)
Wow, so now I'm confused. I understand what you're saying, Jason, and that's the line of code in red that I'm adding.

However, I don't understand what Peter is saying. ASP.NET is going to set that Thread's Current Principal "after AuthenticateRequest" anyway (using the same line of code) so why are you (Peter) saying never to do that?

Additional thought: Perhaps in EndRequest or somewhere this Principal should be blown away (or is it already) for security reasons?
Scott Hanselman
Thursday, September 09, 2004 8:46:04 AM (Pacific Standard Time, UTC-08:00)
+1 for Peter clarifying (since I've made a similar call and haven't yet had it bite me in the tail--file under when !if)

Thursday, September 09, 2004 9:43:43 AM (Pacific Standard Time, UTC-08:00)
Yeah, I'm still confused on Peter's point. I did some more Reflector spelunking and found where "ASP.NET" set's the Thread.CurrentPrincipal. It's in DefaultAuthenticationModule.OnEnter which always gets called immediately after Global.asax's AuthenticateRequest method. The call that OnEnter is making? HttpApplication.SetPrincipalOnThread(context1.User); which as Scott expands in the post above is setting the Thread.CurrentPrincipal. So if ASP.NET can (safely?) make that assignment (in several places), why can't I??

To your additional thought, Scott: I don't know if this is what you're asking, but check out HttpApplication.OnThreadLeave(). This calls RestorePrincipalOnThread, which sets Thread.CurrentPrincipal back to whatever it was before. As for the custom principal, at that point there are no more references to it so it's "floating" around on the heap waiting to be GC'd. (unless a reference is stashed in session etc. which would probably be bad idea).
Thursday, September 09, 2004 10:21:31 AM (Pacific Standard Time, UTC-08:00)
Exactly...Peter is telling me NOT to do exactly what I recommended. I need to understand why that is - I don't see how it affects thread local storage yet.
Scott Hanselman
Thursday, September 09, 2004 10:23:54 AM (Pacific Standard Time, UTC-08:00)
Jason, right, I don't store it anywhere BUT on the thread, so it should get GC'ed happily. The whole point of my custom principal was that it contain information that was more appropriate at the "call" level than the "session" level.

I also agree with you Jason, I'm just moving up the assignment to an earlier point, although as you point out, it will get blown away in the RestorePrincipalOnThread so my security concerns aren't a problem.
Scott Hanselman
Thursday, September 09, 2004 10:33:51 AM (Pacific Standard Time, UTC-08:00)
I've gotten nailed by the same kind of thing with IIS Windows integrated login (impersonation disabled) and careless use of HttpContext.Current.User / WindowsIdentity.GetCurrent / Thread.CurrentPrincipal. So glad you can easily turn impersonation OFF in ASP.NET!

This is one where if you don't know the pipeline, it's just maddening to try and figure out what's going on. There are still many subtle inconsistencies, too...
Matt Davis
Friday, September 10, 2004 6:12:27 AM (Pacific Standard Time, UTC-08:00)
I think I found some potential problems with assigning Thread.CurrentPrincipal to HttpContext.Current, as alluded to by Peter. I'm still researching, but will post my findings on my blog and put a link to it here when I'm done.
Friday, September 10, 2004 11:26:22 AM (Pacific Standard Time, UTC-08:00)
If Peter's warning is indeed true, I can't figure out why. The results of some additional analysis can be found on my blog here:
http://weblogs.threepines.net/taba/archive/2004/09/10/270.aspx

My conclusion so far is that I've yet to find any evidence to back up Peter's claim.
Monday, September 13, 2004 6:39:22 PM (Pacific Standard Time, UTC-08:00)
Ugh - I just typed a huge explaination and it bombed on save - arg!!! Here goes again... but shorter :)

I believe the missing element to this conversation is the fact that the HttpContext.Current.User and the Thread.CurrentPrincipal have very different purposes. It is very acceptible and common for the Current.User and CurrentPrincipal to be different - it's called delegation. Delegation, in my experience, is used most when an application requires Sql Server connections, widnows authentication, and trusted connections. This is because connection pooling is not as effective under these connections.

Anyways, take a look at the system.web's identity element for more information. In general, setting the CurrentPrincipal is going to be reserved for WinForm application delegation, which tends to be rare itself. Whoever is attempting to get the custom principal from the Thread.CurrentPrincipal should be informed that user context should be gathered from the HttpContext.
Ryan Cromwell
Comments are closed.

Contact

Sponsors

Hosting By

On this page...

Tags

Calendar

<January 2009>
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567

Archives

Google Ads