0

My site works fine with IIS, but IIS Express is failing.

tl:dr; How do I configure two separate .NET 4.8 Framework apps in IIS Express under the same domain, as virtual directories, but one using Windows Auth and the other using Forms Auth? Right now Windows Auth is overriding them both.

I have one site/domain with two virtual directories, each pointing to a different app:

- domain.com
  - virualDirectory1 (/app1windows)
  - virtualDirectory2 (/app2forms)

app1windows uses WindowsAuthentication, gets the user's identity from Active Directory, and then creates a FormsAuthentication cookie. This app then redirects to /app2forms/handler.ashx, which reads the cookie, performs some logic, then sets a new FormsAuthentication cookie with the additional information available in app2forms only. All of this works fine. I'm even setting HttpContext.Current.User as a new GenericPrincipal with FormsIdentity.

The problem arises when /app2forms then tries to navigate to a page and passes through Application_AuthenticateRequest . . . here, the HttpContext.Current.User.Identity is always of type System.Security.Principal.WindowsIdentity, despite the fact that Windows authentication should be turned OFF for this app.

In my web.config for /app2forms it very clearly states:

<system.web>
    <authentication mode="Forms">
        <forms cookieless="UseCookies" name="cookieName" path"/" etc. />
...

I have right-clicked on the project file for /app2forms and explicitly set Windows Authentication to Disabled.

In app2forms's .CSPROJ file I have set <IISExpressWindowsAuthentication>disabled</IISExpressWindowsAuthentication>.

I have opened up the applicationhost.config file and set unique applicationPools for each virtual directory.

Nowhere in my applicationhost.config file is windowsAuthentication enabled at all except for app1windows explicitly.

I need these apps to run concurrently under the same domain with same machineKey set in their web.config files, or else cookies won't be recognized across the apps.

Why/how is app2forms seeing any kind of WindowsIdentity anywhere? How do I make one app recognize WindowsIdentity while the other uses FormsIdentity? Please help.

codeMonkey
  • 4,134
  • 2
  • 31
  • 50

1 Answers1

0

Well, rather than try to fight it, I decided to roll with it. I found this answer by @dean-harding that suggested grabbing the forms cookie whenever WindowsIdentity is detected. So here's my solution:

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    if (HttpContext.Current.User != null)
    {
        if (HttpContext.Current.User.Identity.IsAuthenticated)
        {
            if (HttpContext.Current.User.Identity is FormsIdentity)
            {
                FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
                FormsAuthenticationTicket ticket = id.Ticket;

                string userData = ticket.UserData;
                string[] roles = userData.Split(',');
                HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
            }
            else if (HttpContext.Current.User.Identity is WindowsIdentity)
            {
                var oldTicket = ExtractTicketFromCookie(HttpContext.Current, FormsAuthentication.FormsCookieName);
                if (oldTicket != null && !oldTicket.Expired)
                {
                    var ticket = oldTicket;
                    if (FormsAuthentication.SlidingExpiration)
                    {
                        ticket = FormsAuthentication.RenewTicketIfOld(oldTicket);
                        if (ticket == null)
                            return;
                    }
            
                    string userData = ticket.UserData;
                    string[] roles = userData.Split(',');
                    HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(ticket), roles);
                    if (ticket != oldTicket)
                    {
                        // update the cookie since we've refreshed the ticket
                        string cookieValue = FormsAuthentication.Encrypt(ticket);
                        var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName] ??
                                    new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue) { Path = ticket.CookiePath };
            
                        if (ticket.IsPersistent)
                            cookie.Expires = ticket.Expiration;
                        cookie.Value = cookieValue;
                        cookie.Secure = true;
                        cookie.SameSite = SameSiteMode.Lax;
                        cookie.HttpOnly = true;
                        if (FormsAuthentication.CookieDomain != null)
                            cookie.Domain = FormsAuthentication.CookieDomain;
                        HttpContext.Current.Response.Cookies.Remove(cookie.Name);
                        HttpContext.Current.Response.Cookies.Add(cookie);
                    }
                }
            }
        }
    }
}

private FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name)
{
    FormsAuthenticationTicket ticket = null;
    string encryptedTicket = null;

    var cookie = context.Request.Cookies[name];
    if (cookie != null)
    {
        encryptedTicket = cookie.Value;
    }

    if (!string.IsNullOrEmpty(encryptedTicket))
    {
        try
        {
            ticket = FormsAuthentication.Decrypt(encryptedTicket);
        }
        catch
        {
            context.Request.Cookies.Remove(name);
        }

        if (ticket != null && !ticket.Expired)
        {
            return ticket;
        }

        // if the ticket is expired then remove it
        context.Request.Cookies.Remove(name);
        return null;
    }

    return ticket;
}
codeMonkey
  • 4,134
  • 2
  • 31
  • 50