1

I have an MVC4 application whereby I have assigned roles to my user using a custom role provider so that when I check User.IsInRole against my User table it determines which links etc to display on screen in my _Layout.cshtml page. This is working on the Layout page in that the correct links are appearing.

However when I secure my Admin controller using the

[Authorize(Roles = "Admin")]

I am getting the following stack trace from an object not set to instance of an object error:

[NullReferenceException: Object reference not set to an instance of an object.]
System.Web.Mvc.AuthorizeAttribute.AuthorizeCore(HttpContextBase httpContext) +39
System.Web.Mvc.AuthorizeAttribute.OnAuthorization(AuthorizationContext filterContext) +159
System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor) +96
System.Web.Mvc.Async.<>c__DisplayClass25.<BeginInvokeAction>b__1e(AsyncCallback asyncCallback, Object asyncState) +446
System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state) +302
System.Web.Mvc.<>c__DisplayClass1d.<BeginExecuteCore>b__17(AsyncCallback asyncCallback, Object asyncState) +30
System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +382
System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +317
System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +15
System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__2(AsyncCallback asyncCallback, Object asyncState) +71
System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +249
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +50
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +16
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

What exactly is in this Filter context? This works without any further configuration when I use ADFS or Forms based authentication but when using Windows based authentication I have had to do the following to get IsInRole method working:

this.UserName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;

if (this.UserName.Contains("\\"))
{
  string[] stringArray = this.UserName.Split(new Char[] { '\\' });
  this.UserName = stringArray[1];

  MyUser identity = userRepository.Get(u => u.Username == this.UserName).FirstOrDefault();
  HttpContext.Current.User = identity;
}

Do I need to configure some other HttpContext proper in order for Authorize attribute to work in same manner as IsInRole method?

Mike Beeler
  • 4,081
  • 2
  • 29
  • 44
Jay
  • 3,012
  • 14
  • 48
  • 99
  • Anyone able to help me with this? 50 point bounty now available thanks – Jay Dec 04 '13 at 11:18
  • Are you able to put a breakpoint just before you get this.UserName? I'm guessing GetCurrent() is returning a null. FilterContext will give you all you need to write your filter, which will include the httpcontext and route dictionary. Have a look at http://msdn.microsoft.com/en-us/library/dd381609(v=vs.100).aspx for different filter contexts. – Jun Wei Lee Dec 04 '13 at 13:06
  • GetCurrent is returning the username and I am correctly able to get the username, however from this the authorization context and the current user context is not being set as is done in ADFS and Forms all that was required was to get the username which I am able to do here i guess i am missing a step – Jay Dec 04 '13 at 15:38

1 Answers1

0

In the forms case, it could be anything but it is very common to implement a username password form with a lookup by username in a user table, based on the code presented it looks like the repository expects just a username, it just turns out that windows.identity.name returns domain\user. That's where the extra effort comes in to split into domain, user. example below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication6
{
    public class DemoAuthAttribute : AuthorizeAttribute
    {
        // create a file like auth.cs in the mvc project 
        // called 
        //    [DemoAuth("BAR")]
        // as an attibute on a controller method

    private string _role;

    public DemoAuthAttribute(string role)
    {
        _role = role; // should be exapanded to handle more than one
    }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        return httpContext.Request.IsAuthenticated &&  _role == "FOO";
        // lookup the current user in database does the user have role as specificed by the attribute?
        // if yes sucess if not fail.
    }
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        if (AuthorizeCore(filterContext.HttpContext))
        {
            // your custom logic here
            string text = string.Format("<u><h5>Auth successfull.....</h5></u></br>");
            filterContext.HttpContext.Response.Write(text);
        }
        else
        {
            // RedirectResult, etc.
            string text = string.Format("<u><h5>Auth unsuccessfull.....</h5></u></br>");
            filterContext.HttpContext.Response.Write(text);
        }
    }

}

}

Mike Beeler
  • 4,081
  • 2
  • 29
  • 44
  • I have gotten the username alone and split the domain part so that i get just the username and this works for the .IsInRole method but not in the Authorize method. I think it relates to there not being an authorization context set up and I am wondering how I could do this so that the current user is associated with the custom role provider in the same manner that it does when authenticating through ADFS and Forms – Jay Dec 04 '13 at 14:10
  • ok I understand. is the custom provider one that you wrote or thirdparty/open source? – Mike Beeler Dec 04 '13 at 15:02
  • Its one that I have written, works for ADFS and Forms authenticated users I am just not sure how to get the authorization context for the user based on the way I am setting the HttpContext.Current.User which works well for User.IsInRole – Jay Dec 04 '13 at 15:04
  • With forms and ADFS authentication the user details are entered into a login page and then these details are authenticated against an existing user in the database in a table called MyUser. This user has a roleid associated with a role held in table called Role which is used for my role provider. I need to authenticate whether the user is found in the database and then do some sort of authentication so they are assigned a security context where their role can be checked using the [Authorize(Roles = "blah,blah,blah") – Jay Dec 04 '13 at 15:46
  • You need to override authorize core so that you can implement your own logic to handle the Authorize(Roles="x") with your own logic. see http://blog.dkmulligan.com/2013/01/23/asp-net-mvc-4-authorization-filter/ – Mike Beeler Dec 04 '13 at 16:25
  • anyway of creating an iprincipal from the windows name and using this to find the relevant user in my db and the associated role? – Jay Dec 04 '13 at 16:33
  • 1
    HttpContext.Current.User = new System.Security.Principal.GenericPrincipa(id, roles); where roles is a list of strings for roles – Mike Beeler Dec 04 '13 at 16:58
  • Using your suggestion I am at least able to tie the user to the role, is there anyway of setting the isauthenticated to true for the user identity that I am creating I am doing this: WindowsIdentity identity = new WindowsIdentity(this.UserName); string[] roles = new string[1]; roles[0] = myUser.Role.Name; – Jay Dec 04 '13 at 17:26
  • I am trying to access /Admin page and this shows as 401 restricted access when using ADFS or Forms authentication, but I have a logon on the controller that checks that the user is authenticated in this case it is still set to false and it is read only so dont seem able to set it programmatically – Jay Dec 04 '13 at 17:29
  • with MVC this can be implemented as a filter you have the user it's just a question of adding the role and overriding http://stackoverflow.com/questions/4837103/asp-net-mvc-alternative-to-role-provider – Mike Beeler Dec 04 '13 at 17:56