1

I am trying to do something a bit hacky - but feels like it might be doable.

Essentially I have an ASP.Next application that uses Windows authentication.

In some scenarios, I want privileged (admin) users to be able to "spoof" different users (use different names, and assign themselves fewer application roles). The info about the user and roles to spoof are passed in HTTP headers on the request.

What I wanted was for the application to first use Windows to authenticate that the original user is an admin (and thus is allowed to spoof other users). And then set the HTTP context's user property to a generic principal (built from info from the request), so that following code would be essentially oblivious to the spoofing.

I have put the code that changes the User in a System.Web.IHttpModule (see code below).

When using other authentication modes (e.g. Forms) the following code works; but with windows authentication, although everything looks good in the debugger (I see the code being executed and the request has been authenticated) by the time EndRequest is invoked, the request response always ends up as 401. (If I comment out the line that sets the user, then everything is fine).

What I would like to know is; where is the code that is setting the response code to 401? And can it be avoided / disabled? Is what I am attempting just not possible?

Many thanks

using System;
using System.Security.Principal;
using System.Web;
using System.Net;

namespace ACME
{
    public sealed class SpoofModule : IHttpModule
    {
        public void Init(HttpApplication context)
        {
            context.AuthenticateRequest += ScrapeFlowedUser;
            context.EndRequest += HandleEndRequest;
            context.AuthorizeRequest += ScrapeFlowedUser;
        }

        public void HandleEndRequest(object sender, EventArgs e)
        {
            if (HttpContext.Current.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
            {
                // Always end up here if I set HttpContext.Current.User to any other user.
            }
        }

        private void ScrapeFlowedUser(object sender, EventArgs e)
        {
            var userIdentity = HttpContext.Current.User?.Identity;
            if (userIdentity.IsAuthenticated && userIdentity.Name == "ACME\\Admin")
            {
                HttpContext.Current.User = CreateUserFromDataOnRequest();
            }
        }

        private GenericPrincipal CreateUserFromDataOnRequest()
        {
            string userName = "JoeBlogs"; // the name and roles will be passed in the request headers.
            string[] roles = new[] { "Role1", "Role2" };
            var id = new GenericIdentity(userName);  
            return new GenericPrincipal(id, roles); // The generic principal has IsAuthenticated == true.
        }

        public void Dispose()
        {
        }
    }
}

Web config

    <modules>
      <add name="Spoof" type="ACME.SpoofModule, ACME.App" />
    </modules>
Chris Fewtrell
  • 7,555
  • 8
  • 45
  • 63
  • Rather than modifying HttpContext.Current.User, have you thought about just having your code read the current user from some other location that you have complete control over, such as `HttpContext.Items["EffectiveUser"]`? – mason Nov 01 '19 at 20:36
  • Thanks @mason for the reply. I would prefer to set the `Context.User` because then all other code can remain unchanged (and some code may not written by me - e.g. logging libraries that output current request user). But if I cannot do what I would really like to, I probably will investigate doing what you suggest - thanks. – Chris Fewtrell Nov 04 '19 at 11:05

0 Answers0