Geeks With Blogs
Jamie Kurtz Promoting architectural simplicty

The challenge I was facing was being able to accomplish the following within a single ASP.NET application:

  1. Use an NT authenticated connection to my SQL Server database; this would assume the identity set for the IIS application pool
  2. Disable anonymous access to my web site; only allow Windows Authentication
  3. Force all use of the TFS API to happen within the context of the Windows authenticated user

First, in ASP.NET, in order to force the execution context to use the identity of the person browsing your site, you add the <identity impersonate="true" /> element to your web.config file - along with disallowing anonymous access to the site.

Second - and separately, if you want to utilize NT authentication for your connection to SQL Server, you use a connection string something like this:

"Data Source=server;Initial Catalog=database;Integrated Security=True;"

Then, to specify a certain Windows account for the SQL Server connection, you simply set the application pool identity to that account. In other words, since a) the connection string specifies that "integrated security" or Windows authentication be used, and b) your application - i.e. the w3wp.exe process - is running under the specified Windows account, then your connection to SQL Server will use that account. Simple enough!

The problem here is that I also wanted my ASP.NET application to use the TFS API, which - for proper permissions - requires that the user being authenticated on my web site be the account "running" the ASP.NET code. However, if I were to add the <identity impersonate="true" /> element to my web.config file, then all my connections to SQL Server would use that authenticated account - which I didn't want. I still wanted to use the Windows account specified in the IIS application pool to connect to the database.

So... the solution I came up with was this:

  1. Set the web site security to disallow anonymous access. This forces IIS to set the thread identity to the authenticated user.
  2. Keep the web site impersonation off - i.e. make sure <identity impersonate="false" /> is present in your web.config file's system.web section.
  3. Use the integrated security connection string shown above
  4. Set the application pool identity to the Windows domain account I want for my SQL Server access
  5. (Here's the cool part!) Create an IDisposable class that is used to impersonate the current thread's identity
  6. (Cool part number 2!) Use that class within a using() block for all code that uses the TFS API

Here's the class - very simple... 

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Security.Principal;

namespace WebSite
{
    /// 
    /// This class can be used to impersonate the Windows account accessing 
    /// the IIS web site or virtual directory. When this object goes out of scope, the
    /// impersonation is reverted back to the original identity.
    /// 
    public class HttpContextImpersonator : IDisposable
    {
        private HttpContextImpersonator()
        {
        }

        private WindowsImpersonationContext ctx = null;

        public static HttpContextImpersonator Begin()
        {
            HttpContextImpersonator impersonator = new HttpContextImpersonator();
            WindowsIdentity winId = (WindowsIdentity)HttpContext.Current.User.Identity;
            impersonator.ctx = winId.Impersonate();
            return impersonator;
        }

        public void Undo()
        {
            if (this.ctx != null)
            {
                this.ctx.Undo();
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            try
            {
                this.Undo();
            }
            catch
            {
                // eat it
            }
        }

        #endregion
    }
}
Then, to use the new class when connecting to TFS you just put your TFS code within a using() block with the HttpContextImpersonator class. In this particular example I was retrieving the Uri for a Team Project (I don't remember why this was needed - but it worked).
        public static string GetTFSProjectUri(string teamProject, string tfsUrl)
        {
            using (HttpContextImpersonator impersonator = HttpContextImpersonator.Begin())
            {
                TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer(tfsUrl);
                ICommonStructureService css = (ICommonStructureService)tfs.GetService(typeof(ICommonStructureService));

                ProjectInfo[] projects = css.ListProjects();

                foreach (ProjectInfo projectInfo in projects)
                {
                    if (string.Equals(teamProject, projectInfo.Name, StringComparison.CurrentCultureIgnoreCase))
                    {
                        return projectInfo.Uri;
                    }
                }

                return null;
            }
        }

Or, for a simpler example, just populate a label with the current user: 

            using (HttpContextImpersonator impersonator = HttpContextImpersonator.Begin())
            {
                this.lblCurrentUser.Text = WindowsIdentity.GetCurrent().Name;
            }

Without the HttpContextImpersonator using() block the label would contain the account of the application pool identity.

So, pretty simple, eh?!?!

Posted on Monday, August 27, 2007 1:53 PM | Back to top


Comments on this post: ASP.NET impersonation to TFS - while using NT authentication to SQL Server

# re: ASP.NET impersonation to TFS - while using NT authentication to SQL Server
Requesting Gravatar...
I had a similar problem. We have a website with Windows Authentication on, but impersonation off. From within this website, I need to call a web service and pass the user credentials of the current user. However, passing the credentials using System.Net.CredentialCache.DefaultCredentials always resulted in the credentials of the website and not the user (because impersonation is off).

Using your class, I can simply impersonate the current user before setting the credentials on the web service proxy and call the appropriate method.

It works great. Thanks

Left by Dan Ibarra on Mar 22, 2008 10:17 AM

Your comment:
 (will show your gravatar)


Copyright © Jamie Kurtz | Powered by: GeeksWithBlogs.net