Impersonate using Forms Authentication
K

5

24

I have an ASP.NET site that must use Forms Authentication and not Windows Authentication to access a ActiveDirectoryMembershipProvider. The site must use forms because they need a designed input form instead of the browser authentication popup that Windows authentication uses.

The site needs to impersonate the user logged in via Active Directory to access user specific files.

However, the WindowsIdentity.GetCurrent() is not the same as the HttpContext.Current.User.Identity although my web.config contains:

<authentication mode="Forms">
    <forms loginUrl="login.aspx" timeout="480"/>
</authentication>
<identity impersonate="true" />

I cannot use LoginUser() and the WindowsIdentity.Impersonate() because I need to impersonate as the AD user to get their specific permissions, and I don't know the user's password because Forms takes care of logging in.

Is it possible maybe from the login.aspx.cs, to take the System.Web.UI.WebControls.Login.Password, then save the LoginUser() token in a session variable for WindowsIdentity.Impersonate() later? Or maybe a much more secure method of Impersonating the right way?

I'm confused why Forms authentication can't automatically <identity impersonate="true" />

I've read this http://msdn.microsoft.com/en-us/library/ms998351.aspx but it uses Windows Authentication.

Kodiak answered 30/6, 2009 at 21:52 Comment(1)
Here's the workaround I used: I gave IUSER_ access to the files, then I check get the permissions of every file or folder via DirectorySecurity.GetAccessRules(). If the FileSystemAccessRule.Value == "DOMAIN\\"+Page.User.Identity.Name in the access rules, then I add that file or folder to a list. Lastly I display the list of files. So instead of Impersonating, I give the IUSR_ full access, and check permissions manually on the things I need that user to access.Kodiak
M
19

Impersonating a user using Forms Authentication can be done. The following code does work.

The Visual Studio Magazine article referred to by Robert is an excellent resource. There are a some issues with the example code in the article, so I've included some working code below.

Note: If you are using Visual Studio, make sure to launch it "Run as Administrator" to avoid problems with UAC blocking impersonation.

// in your login page (hook up to OnAuthenticate event)
protected void LoginControl_Authenticate(object sender, AuthenticateEventArgs e)
{
    int token;
    // replace "YOURDOMAIN" with your actual domain name
    e.Authenticated = LogonUser(LoginUser.UserName,"YOURDOMAIN",LoginUser.Password,8,0,out token);
    if (e.Authenticated) {
        Session.Add("principal", new WindowsPrincipal(new WindowsIdentity(new IntPtr(token))));
    }
}

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword,
    int dwLogonType, int dwLogonProvider, out int TokenHandle);


// in global.asax.cs
void Application_PreRequestHandlerExecute(object send, EventArgs e)
{
    if (Thread.CurrentPrincipal.Identity.IsAuthenticated == true && HttpContext.Current.Session != null) {
        WindowsPrincipal windowsPrincipal = (WindowsPrincipal)Session["principal"];
        Session["principal"] = (GenericPrincipal)Thread.CurrentPrincipal;
        Thread.CurrentPrincipal = windowsPrincipal;
        HttpContext.Current.User = windowsPrincipal;
        HttpContext.Current.Items["identity"] = ((WindowsIdentity)windowsPrincipal.Identity).Impersonate();
    }
}

// in global.asax.cs
void Application_PostRequestHandlerExecute(object send, EventArgs e)
{
    if (HttpContext.Current.Session != null && Session["principal"] as GenericPrincipal != null) {
        GenericPrincipal genericPrincipal = (GenericPrincipal)Session["principal"];
        Session["principal"] = (WindowsPrincipal)Thread.CurrentPrincipal;
        Thread.CurrentPrincipal = genericPrincipal;
        HttpContext.Current.User = genericPrincipal;
        ((WindowsImpersonationContext)HttpContext.Current.Items["identity"]).Undo();
    }
}

// test that impersonation is working (add this and an Asp:Label to a test page)
protected void Page_Load(object sender, EventArgs e)
{
    try {
        // replace YOURSERVER and YOURDB with your actual server and database names
        string connstring = "data source=YOURSERVER;initial catalog=YOURDB;integrated security=True";
        using (SqlConnection conn = new SqlConnection(connstring)) {
            conn.Open();
            SqlCommand cmd = new SqlCommand("SELECT SUSER_NAME()", conn);
            using (SqlDataReader rdr = cmd.ExecuteReader()) {
                rdr.Read();
                Label1.Text = "SUSER_NAME() = " + rdr.GetString(0);
            }
        }
    }
    catch {
    }
}

Update:

You should also handle Application_EndRequest, because calls like Response.End() will bypass Application_PostRequestHandlerExecute.

Another issue is that the WindowsIdentity may get garbage collected, so you should create a new WindowsIdentity and WindowsPrincipal from the logon token on every request.

Update2:

I'm not sure why this is getting downvoted, because it works. I've added the pinvoke signature and some test code. Again, launch Visual Studio using "Run as Administrator". Google how to do that if you don't know how.

Metalinguistics answered 8/8, 2012 at 21:52 Comment(4)
FWIW, (I'm the question OP) I upvoted this answer and changed it to the correct answer.Kodiak
This approach didnt work for MVC in my tests, but I managed do that overriding BeginExecute and EndExecute from System.Web.Mvc.Controller. The problem of this approach is that I need put this in na BaseController and change all of existing controller to inherit from this BaseControllerPontius
I'm using MVC as well. I had to comment out the Session["principal"] = (GenericPrincipal)Thread.CurrentPrincipal; line because it couldn't convert a web.PrincipalRole to a GenericPrincipal. It didn't really matter though because I only needed to impersonate a user when connecting to the database so I didn't need to store the impersonated user for use throughout the site. Which means I also didn't put anything in the global.asax.cs I just included this in my db class.Nuli
I would also like to add that you need to bind the Application.PostRequestHandlerExecute and Application.PreRequestHandlerExecute to Application_PostRequestHandlerExecute and Application_PreRequestHandlerExecute methods respectivelyHelvetii
P
0

If your users are using IE then you can turn on integrated security for the website and your users will be authenticated silently (no login dialog, no login page). Your impersonation will then work. If you need to target other browsers then this may not work (the user will probably be presented with a login dialog).

Your current impersonation will never work because your users are logging in using an account other than their domain account. You can't expect the site to impersonate a user which hasn't supplied his credentials. That would go against basic security principals.

Practice answered 30/6, 2009 at 22:24 Comment(1)
It's an extranet, so the users access internal files using AD accounts from outside the LAN. When using an ActiveDirectoryMembershipProvider the users are logging in using their domain account. The actual user store is AD, they can log in using Forms or Windows authentication successfully, but cannot access the files when using Forms, only when using Windows because Forms uses the IUSR_* account.Kodiak
S
0

You may find this useful:

EDIT

On reading your question more closely, I am not sure if that approach would work with your scenario though; when you login using Forms Authentication and Impersonate Active Directory user

Selfdefense answered 1/7, 2009 at 12:10 Comment(0)
D
0

We got the same problem recently, the customer wanted their users can log in by AD account and then this credential must be used to access Analysis Service as well as all other databases. They wanted it that way because they implemented an auditing system and all access must be done by current logged in account.

We tried Forms authentication and Win32 LogonUser() API for impersonating part, it worked but it also asks us for user's password as plain text. Later, we decided to utilized Windows authentication, it saves us lot of time (no more AD authentication, impersonate manually). Of course, there was also no fancy login page.

Diaghilev answered 17/9, 2011 at 16:41 Comment(0)
G
-1

For just in case, and a bit late, i found something that works for me and it's really simple but of course is only for testing purposes...

Just set a cookie with your username.

//Login button. You can give whatever input to the form
protected void Login_Click(object sender, EventArgs e)
{
    FormsAuthentication.SetAuthCookie("your_username", createPersistentCookie: true);
    Response.Redirect("~/");
}

Any comments accepted...

Glantz answered 16/12, 2014 at 5:18 Comment(1)
This question is about how to get WindowsIdentity.Impersonate() to work. I don't see how this answer attempts to answer that questionUsage

© 2022 - 2024 — McMap. All rights reserved.