Hybrid of Windows Authentication and Forms Authentication in ASP.NET MVC 4
Asked Answered
I

1

9

We have an ASP.NET MVC 4 intranet application. We’re using Windows Authentication and that aspect works fine. The user’s credentials are used and we can access those credentials from the web app.

What we really want is some sort of hybrid mode, however. We want to get the user’s credentials from the browser, but we also want to verify that the user is in our application’s database. If the user’s in the database, then they can just continue on. If they’re not, we want to redirect them to a page asking for alternate credentials. What I’m doing now is, in Global.asax.cs, I’ve got an Application_AuthenticateRequest method and I’m checking to see if the user is authenticated. If they are and their cookie information doesn’t reflect the fact that they’re logged into the system, then I log them in and set up some cookies with info about the user. If they’re not authenticated, I redirect them to a login page. We can’t use AD roles for reasons involved with company policy, so we need to use the database for additional authentication.

I’m guessing Application_AuthenticateRequest isn’t the place to do this, but maybe it is. But we basically need a place to filter the requests for authentication. But additionally this implementation leads me to another issue:

We have certain URLs in our app that allow anonymous access. I’ve added <location> tags to the web.config for these. The problem is, when anonymous calls are made into these, it gets to Application_AuthenticateRequest and tries to log the user into the DB. Now, I can add code into Application_AuthenticateRequest to handle these URLs and that’s currently my plan, but if I’m write and Application_AuthenticateRequest isn’t the place to be doing this, then I’d rather figure it out now than later.

Isis answered 3/7, 2013 at 18:21 Comment(0)
T
5

You need to use Action Filters for this purpose. You can extend the AuthorizeAttribute like this:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    private UnitOfWork _unitOfWork = new UnitOfWork();

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var isAuthorized = false;
        var username = httpContext.User.Identity.Name;
        // Some code to find the user in the database...
        var user = _unitOfWork.UserRepository.Find(username);
        if(user != null)
        {
           isAuthorized = true;
        }


        return isAuthorized;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {            
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        if (AuthorizeCore(filterContext.HttpContext))
        {
            SetCachePolicy(filterContext);
        }
        else
        {
           // If not authorized, redirect to the Login action 
           // of the Account controller... 
          filterContext.Result = new RedirectToRouteResult(
            new System.Web.Routing.RouteValueDictionary {
               {"controller", "Account"}, {"action", "Login"}
            }
          );               
        }
    }

    protected void SetCachePolicy(AuthorizationContext filterContext)
    {
        // ** IMPORTANT **
        // Since we're performing authorization at the action level, 
        // the authorization code runs after the output caching module. 
        // In the worst case this could allow an authorized user 
        // to cause the page to be cached, then an unauthorized user would later 
        // be served the cached page. We work around this by telling proxies not to 
        // cache the sensitive page, then we hook our custom authorization code into 
        // the caching mechanism so that we have the final say on whether a page 
        // should be served from the cache.
        HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
        cachePolicy.SetProxyMaxAge(new TimeSpan(0));
        cachePolicy.AddValidationCallback(CacheValidationHandler, null /* data */);
    }

    public void CacheValidationHandler(HttpContext context,
                                        object data,
                                        ref HttpValidationStatus validationStatus)
    {
        validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
    }
}

Then, you can use this attribute at the Controller level or Action level like this:

[MyAuthorize]
public ActionResult SomeAction()
{
  // Code that is supposed to be accessed by authorized users only
}
Thesda answered 3/7, 2013 at 19:21 Comment(5)
I like this solution, but I still get 401 errors on controller methods that should allow anyone. In this particular case, I'm calling it from a C# HttpWebRequest.GetResponse() call. DebugController.FlushCaches() has [AllowAnonymous] and in the web.config I have a <location> tag for Debug/FlushCaches that has <allow users="*"/>. But when my HttpWebRequest calls it, I get a 401.Isis
Use this as your <location> tag: <location path="Debug/FlushCaches"> <system.web> <authorization> <allow users="*"/> </authorization> </system.web> </location>Thesda
#15088255Thesda
that's what I have in my config file as I tried to explain in the comment right above yours.Isis
I found the problem. We had disabled anonymous authentication in IIS. I re-enabled it and everything seems to be working again. Thanks.Isis

© 2022 - 2024 — McMap. All rights reserved.