Question

I am building a intranet application using MVC3 with a MSSQL backend. I have authentication and roles (through a custom roles provider) working properly. What I am trying to do now is overriding User.Identity to allow for items like User.Identity.FirstName. But I cannot find any code that will show me how do this in WindowsIdentity

I have tried writing a custom provider:

public class CPrincipal : WindowsPrincipal
{
    UserDAL userDAL = new UserDAL();
    public CPrincipal(WindowsIdentity identity)
        : base(identity)
    {
        userInfo = userDAL.GetUserProfile(identity.Name.Split('\\')[1]);
        this.identity = identity;
    }
    public UserInfo userInfo { get; private set; }
    public WindowsIdentity identity { get; private set; }
}

and overriding the WindowsAuthentication to populate the custom principal.

    void WindowsAuthentication_OnAuthenticate(object sender, WindowsAuthenticationEventArgs e)
    {
        if (e.Identity != null && e.Identity.IsAuthenticated)
        {
            CPrincipal cPrincipal = new CPrincipal(e.Identity);
            HttpContext.Current.User = cPrincipal;
        }
    }

I have a breakpoint in the authentication function and the principal is being populated; however, when I put a breakpoint in the controllers, the User is just its normal RolePrincipal, instead of my custom principal. What am I doing wrong?

EDIT:

I commented out the code above in the global.asax. I have overridden the AuthorizeAttribute using C#:

public class CAuthorize : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        bool authorized = base.AuthorizeCore(httpContext);
        if (!authorized)
        {
            return false;
        }


        IIdentity user = httpContext.User.Identity;
        CPrincipal cPrincipal = new CPrincipal(user);
        httpContext.User = cPrincipal;

        return true;
    } 

}

And adjusted my principal to the following:

public class CPrincipal : IPrincipal
{
    private UserDAL userDAL = new UserDAL();
    public CPrincipal(IIdentity identity)
    {
        userInfo = userDAL.GetUserProfile(identity.Name.Split('\\')[1]);
        this.Identity = identity;
    }
    public UserInfo userInfo { get; private set; }

    public IIdentity Identity { get; private set; }

    public bool IsInRole(string role)
    {
        throw new NotImplementedException();
    }
}

Now I when I put a breakpoint in, the watch shows the following in user:

  • User
    • [CSupport.Model.CPrincipal]
    • Identity

Identity is accessable; however, it is still the WindowsIdentity CPrincipal is only accessible in the watch and not accessible directly.

EDIT: Thanks to everyone who contributed to this. You have greatly expanded my understanding of how the various parts work.

I got both ways to work, so I thought I would share.

Option 1: Override the Authorize Request in Global.asax

This is the one I am going with.

I did not use Application_AuthenticateRequest because (according to this: HttpContext.Current.User is null even though Windows Authentication is on) the user has not been populated in a Windows authentication process and thus there is nothing that I can use to go get the user information.

Application_AuthorizeRequest is the next in the chain and happens after the windows identity is brought in.

    protected void Application_AuthorizeRequest(object sender, EventArgs e)
    {
        if (User.Identity.IsAuthenticated && Roles.Enabled)
        {
            Context.User = new FBPrincipal(HttpContext.Current.User.Identity);
        }
    }

This is the override of the Principal

public class CPrincipal : IPrincipal
{
    private UserDAL userDAL = new UserDAL();
    public CPrincipal(IIdentity identity)
    {
        userInfo = userDAL.GetUserProfile(identity.Name.Split('\\')[1]);
        this.Identity = identity;
    }
    public UserInfo userInfo { get; private set; }

    public IIdentity Identity { get; private set; }

    public bool IsInRole(string role)
    {
        return userDAL.IsUserInRole(userInfo.UserName, role);
    }
}

This is how you access the updated info in the new Principal that was created.

    [Authorize(Roles = "super admin")]
    public ActionResult Dashboard()
    {
        string firstname = (User as CPrincipal).userInfo.FirstName; // <--
        DashboardModel dModel = reportDAL.GetChartData();
        return View(dModel);
    }

Option 2: Override the AuthorizeAttribute

This is the overridden Principal (It is the same as above)

public class CPrincipal : IPrincipal
{
    private UserDAL userDAL = new UserDAL();
    public CPrincipal(IIdentity identity)
    {
        userInfo = userDAL.GetUserProfile(identity.Name.Split('\\')[1]);
        this.Identity = identity;
    }
    public UserInfo userInfo { get; private set; }

    public IIdentity Identity { get; private set; }

    public bool IsInRole(string role)
    {
        return userDAL.IsUserInRole(userInfo.UserName, role);
    }
}

Here is the override of the Authorize Attribute

public class CAuthorize : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        bool authorized = base.AuthorizeCore(httpContext);
        if (!authorized)
        {
            return false;
        }


        IIdentity user = httpContext.User.Identity;
        CPrincipal cPrincipal = new CPrincipal(user);
        httpContext.User = cPrincipal;

        return true;
    } 

}

This is where you change which AuthorizeAttribute to use and utilizing the new information.

    [CAuthorize(Roles = "super admin")] // <--
    public ActionResult Dashboard()
    {
        string firstname = (User as CPrincipal).userInfo.FirstName; // <--
        DashboardModel dModel = reportDAL.GetChartData();
        return View(dModel);
    }

Option 1 handles everthing globally, option 2 handles everything at an individual level.

Was it helpful?

Solution

Instead of doing it this way, you should override the Application_AuthenticateRequest method in global.asax, then use Current.User rather than HttpContext.Current.User (not sure why, but there is a difference).

Then, an easy way to access this in your controller is to create an extension method? Something like this:

public static class IIdentityExtensions {
    public static IMyIdentity MyIdentity(this IIdentity identity) {
        return (IMyIdentity)identity;
    }
}

then you can just say User.Identity.IMyIdenty().FirstName. You could probably do this as a property as well.

Here is the code I use:

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    FormsAuthenticationTicket authTicket = FormsAuthentication
       .Decrypt(authCookie.Value);
    var identity = new MyIdentity(authTicket.Name, "Forms", 
       FormsAuthenticationHelper.RetrieveAuthUserData(authTicket.UserData));
    Context.User = new GenericPrincipal(identity, 
       DependencyResolver.Current.GetService<ISecurityHandler>()
          .GetRoles(identity.Name).ToArray());
}

Now, ignoring the DependencyResolver stuff and the custom auth ticket stuff, this is pretty basic and works correctly for me.

Then, in my app, when i'm need info from my custom identity, i just cast it with ((IMyIdentity)User.Identity).FirstName or whatever I need. It's not rocket science, and it works.

OTHER TIPS

What am I doing wrong?

Probably the [Authorize] attribute is overriding your changes. So instead of doing this in the WindowsAuthentication_OnAuthenticate method in your Global.asax write a custom Authorize attribute, like so:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var authorized = base.AuthorizeCore(httpContext);
        if (!authorized)
        {
            return false;
        }


        var user = httpContext.User as WindowsIdentity;
        CPrincipal cPrincipal = new CPrincipal(user);
        httpContext.User = cPrincipal;

        return true;
    }
}

and then use your custom attribute instead of the default one:

[MyAuthorize]
public ActionResult SomeAction()
{
    // User.Identity will be your custom principal here
}

In ASP.NET MVC the standard way to perform authorization is through authorization action filters, not through events in Global.asax.

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