Question

I have a MVC app where I have a User class and the user can also impersonate another user(Admin users only).

So I have this code below that authenticates the request and instantiates my version of a User class.

It then tries to get the impersonated user from the Session object but Session is not available in this method in the global.asax.

Hope this makes sense.

How else could I do this?

My question I guess is at what point in the global.asax methods do you get access to Session object for each request?

protected void Application_OnAuthenticateRequest(object sender, EventArgs e)
{
    IMylesterService service = ObjectFactory.GetInstance<IMylesterService>();

    if (Context.User != null)
    {
        if (Context.User.Identity.IsAuthenticated)
        {
            User user = service.GetUser(Context.User.Identity.Name);
            if (user == null)
                throw new ApplicationException("Context.user.Identity.name is not a recognized user");

            User impersonatedUser = (User)this.Session["ImpersonatedUser"];
            if (impersonatedUser == null)
                user.ImpersonatedUser = user;
            else
                user.ImpersonatedUser = impersonatedUser;

            System.Threading.Thread.CurrentPrincipal = Context.User = user;
            return;
        }
    }
    User guest = service.GetGuestUser();
    guest.ImpersonatedUser = guest;

    System.Threading.Thread.CurrentPrincipal = Context.User = guest;
}

No correct solution

OTHER TIPS

Try creating an authorization filter:

public class CustomAuthorizationFilter : AuthorizeAttribute
{
    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        // perform your authorization here
        // full access to HttpContext and session
    }
}

You can then apply this attribute to your controllers. Ideally you'd have a base controller that all other controllers inherit from and you could apply the attribute at the class level on that controller. Then all of your requests would be authorized and apply the impersonation as you have coded above.

Session will not be available during AuthenticateRequest: What you will need to do is tag the required information to the Identity.userData; so for example if you where using forms authentication do the following:

void Application_AuthenticateRequest(object sender, EventArgs e)
{
   if (Context.User != null)
   {
     if (Context.User.Identity.IsAuthenticated)
     {
       // retrieve the value 
       var id = (FormsIdentity)Context.User.Identity;
       var myvalue = id.Ticket.UserData; // "Here you are"
     }
   }
}

For sign in using forms you will need to write a custom cookie: MVC -> class FormsAuthenticationService : IFormsAuthenticationService

public static void SetAuthenticationCookie(HttpContextBase context, FormsAuthenticationTicket ticket)
{
    var cookie = new HttpCookie(FormsAuthentication.FormsCookieName)
       {
          Value = FormsAuthentication.Encrypt(ticket),
          Secure = FormsAuthentication.RequireSSL,
          Domain = FormsAuthentication.CookieDomain,
          HttpOnly = true,
          Expires = DateTime.Now.AddMinutes(15)
       };

    if (!context.Request.IsSecureConnection && FormsAuthentication.RequireSSL)
    {
        throw new HttpException("Ticket requires SSL.");
    }
    context.Response.Cookies.Add(cookie);
}
public static FormsAuthenticationTicket CreateTicket(HttpContextBase context, string emailAddress, string userData, bool persist)
{
    return new FormsAuthenticationTicket(1, emailAddress, DateTime.Now, DateTime.Now.AddMinutes(15), persist, userData, FormsAuthentication.FormsCookiePath);
}

Finally in SignIn you would now create the required ticket by calling CreateTicket(...), and then you would write it out by SetAuthenticationCookie(...).

public void SignIn(string userName, string password)
{
    if(CheckUserValid(userName,password, out string email))
    {
        var ticket = CreateTicket(email, "Here you are", true);
        SetAuthenticationCookie(HttpContext.Current.Base(), ticket);
    }
}

I have never used it, but assume that session state is assigned during the AcquireRequestState:

public void Init(HttpApplication context)
{
    context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
}

I had this same issue of needing to access session in global.asax and finally solved it by moving my code into the AcquireRequestState handler, which happens after the authentication is passed.

    protected void Application_AcquireRequestState(Object sender, EventArgs e)
    {
        if (Request.IsAuthenticated && Context.Session != null)
        {
            // access Context.Session
        }
    }

This fires a lot and the current context does not always have a valid session object, hence the check.

EDIT: Had to add the check for IsAuthenticated too -- was getting a null error when logged out. Works great now.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow