Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.net Forms Authentication - manually setting HttpContext.Current.User not working in custom AuthorizeAttribute

I've been hitting this one for hours and I'm stumped. I'm making an ajax post request to an MVC 5 controller in an attempt to auto-login a specific pre-defined "super" user. In the controller method, I'm trying to programmatically set the HttpContext.Current.User and authenticate, so the super user can skip the process of manually logging in. The consensus on this seems to be here, which I implemented:

setting HttpContext.Current.User

This seems to work until I try to view any other controller methods with a custom AuthorizeAttribute.

Controller method:

[HttpPost]
[AllowAnonymous]
public ActionResult Login(string username)
{
    string password = ConfigurationManager.AppSettings["Pass"];

    User user = service.Login(username, password);

    var name = FormsAuthentication.FormsCookieName;
    var cookie = Response.Cookies[name]; 
    if (cookie != null)
    {   
        var ticket = FormsAuthentication.Decrypt(cookie.Value);
        if (ticket != null && !ticket.Expired)
        {
            string[] roles = (ticket.UserData as string ?? "").Split(',');
            System.Web.HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(ticket), roles);
        }
    }

    //...processing result

    return Json(result);
}

The service.Login method above creates the cookie:

FormsAuthentication.SetAuthCookie(cookieValue, false);

Though I'm setting the User, which has an Identity and IsAuthenticated is true, the filterContext.HttpContext.User below is not the same user. It's essentially empty as if it was never assigned, and is not authenticated.

public override void OnAuthorization(AuthorizationContext filterContext) 
{
    string[] userDetails = filterContext.HttpContext.User.Identity.Name.Split(char.Parse("|"));
}

The closest post I could find is here: IsAuthenticated works on browser - but not with Air client!

However, the fix for that was already in place for me:

<authentication mode="Forms">
  <forms cookieless="UseCookies" timeout="60" loginUrl="~/Account/Login" />
</authentication>

What am I missing to make the AuthorizationContext.User match the HttpContext.Current.User that I'm authenticating in the controller?

UPDATE:

I realize I need a redirect to set the cookie properly, I'm just not able to make that work remotely, via an ajax call. Here's what the script in site A looks like, when executing the controller method on site B. This redirect doesn't set the session. It still isn't present when authenticating the user on the next controller method. It just redirects me back to the login view.

function remoteLogin(id) {
    $.ajax({
        url: "/MyController/RemoteLogin",
        type: "POST",
        dataType: "json",
        data: { "id": id }
    }).done(function (data) {
        if (data) {
            if (data.user) {
                var user = data.user;
                $.ajax({
                    url: "http://siteB.xyz/Account/Login",
                    type: "POST",
                    dataType: "json",
                    data: { "username": user.username, "password": user.password }
                }).done(function (data) {
                    if (data) {
                        window.location.href = "http://siteB.xyz/Next"
                    } else {
                        alert("Fail.");
                    }
                }).fail(function (data) {
                    alert("Fail.");
                });
            } else {
                alert("Fail.");
            }
        }
    }).fail(function (data) {
        alert("Fail.");
    });
}
like image 564
Tsar Bomba Avatar asked Dec 02 '15 03:12

Tsar Bomba


People also ask

How do you set HttpContext user identity for an application manually?

You can achieve this by manually settings HttpContext. User: var identity = new ClaimsIdentity("Custom"); HttpContext. User = new ClaimsPrincipal(identity);

What is HttpContext current user identity Name in C#?

It just holds the username of the user that is currently logged in. After login successful authentication, the username is automatically stored by login authentication system to "HttpContext.Current.User.Identity.Name" property.

How does HttpContext current work?

The HttpContext encapsulates all the HTTP-specific information about a single HTTP request. When an HTTP request arrives at the server, the server processes the request and builds an HttpContext object. This object represents the request which your application code can use to create the response.

Why is user identity Isauthenticated false?

identity. isauthenticated is False when a user is already logged in.


2 Answers

The issue has to do with where your action code is running relative to the request processing pipeline. Your code is running in the ProcessRequest step (see below).

The HttpContext.Current.User is expected to be set by the AuthenticateRequest event handler. FormsAuthenticationModule takes care of this, turning the Request.Cookies' FormsAuthenticationCookie back into a FormsAuthenticationTicket and then into an IPrincipal. That Principal gets set as Request.CurrentUser and I believe Thread.CurrentPrincipal

This needs to be done at the AuthenticateRequest step because the request cache can vary by user (ResolveRequestCache) and session state always does (AcquireRequestState). Also, the AuthorizeRequest step makes decisions about whether the user principal set in the AuthenticateRequest step has the permission required to pass through the AuthorizeRequest step without getting a 401 or 300-level redirect to a login page (and I believe MVC and WebAPI's Authorization mechanisms work as part of ProcessRequest)

  • BeginRequest
  • AuthenticateRequest (FormsAuthenticationModule)
  • AuthorizeRequest (e.g., UrlAuthorizationModule)
  • ResolveRequestCache
  • MapRequestHandler
  • AcquireRequestState
  • PreRequestHandlerExecute
  • ProcessRequest (HttpHandler, Web Forms, MVC controller actions live here)

You can try to force this by setting HttpContext.Current.User and Thread.CurrentPrincipal to your IPrincipal, but it will only apply to code running after that point in the ProcessRequest phase... important decisions about session state, cache, and authorization have already been made.

In theory, you could implement something similar to what you're trying to do by writing an HttpModule and implementing it in/before AuthenticateRequest (or global.asax), but you wouldn't yet have access to higher level MVC controller concepts, Session state, etc. You'd be able to inspect HttpContext.Request.QueryString, HttpContext.Request.Form, and HttpContext.Request.Cookies, but not much else. You'd have to grab the username from your AJAX call out of the HTTP request, and either call FormsAuthentication.SetAuthCookie() or create a FormsAuthenticationTicket and FormsAuthenticationCookie yourself and stuff the cookie in the Request.Cookies and Response.Cookies. By the time your controller logic runs, I'm pretty sure the request would appear authenticated.

like image 39
scottt732 Avatar answered Oct 16 '22 17:10

scottt732


The problem you have is at this point you're only setting the authentication cookie, the IPrincipal that gets created inside the forms authentication module will not happen until there is a new request - so at that point the HttpContext.User is in a weird state. Once the redirect happens then, because it's a new request from the browser the cookie will get read before your page is reached and the correct user object created.

Cookies are only set on the browser after a request is completed.

like image 155
yazan_ati Avatar answered Oct 16 '22 16:10

yazan_ati