Workaround for using IIS 7 url authorization with ASP.NET roles

When using the IIS 7.0 Integrated pipeline, you gain access to a ton of cool scenarios where IIS and ASP.NET features work together to provide value for your application – regardless of the application content.

Most of these features involve using ASP.NET features (e.g.Forms Authentication) to provide their services for non-ASP.NET content.  These have been at the core of most of the demos we did while developing the Integrated pipeline, and consequently are the most well known.

The other major class of features are the new IIS 7.0 modules.  For example, the IIS 7.0 Url Authorization module  – a new IIS 7.0 feature that somewhat mirrors the ASP.NET url authorization module.  Like the ASP.NET version everyone knows and loves, the IIS version uses declarative authorization rules (although with a slightly different format).

Here is an example of the IIS 7.0’s Url Authorization configuration section:

  <system.webServer>

      <security>

          <authorization>

              <add accessType="Deny" users="?" />

              <add accessType="Allow" roles="test" />

          </authorization>

      </security>

  </system.webServer>

Here are some key differences between the IIS and ASP.NET versions:

1.       IIS version doesn’t require ASP.NET (e.g. it could be used for declarative security on server core, although with W2k8 R2 you can now you can install ASP.NET).

2.       The IIS version has a slightly different configuration format.

3.       IIS version has an ACL-style rule execution order, where Deny rules are always processed first. The ASP.NET version processes most recently defined rules first. This makes the IIS version more friendly to server admins that want to mandate certain deny rules in the global config and make sure they cannot be overridden by levels below (a feature that I wasn’t a big fan of during IIS development, but I lost to the server admin-centric mentality of the team).

4.       The IIS manager tool by default doesn’t provide the UI for ASP.NET authorization rules (although the downlodable Admin tool extensions finally do), but does for the IIS authorization rules.

In the spirit of the integrated pipeline, you can use IIS url authorization with ASP.NET roles.  Meaning, you can specify roles in the IIS url authorization rules (as in the example below) and if you are using ASP.NET roles, the module will be able to call through to the ASP.NET user IsInRole(…) method.  If you are using windows authentication, the roles will work against the Windows token group membership.  This works out of the box so you can switch to using IIS authorization without losing role support.

Unfortunately, it looks like a bug recently crep
t in where an exception in the user IsInRole() method causes the IIS worker process to crash.  Ops! This has to do with the way unhandled exceptions are handled by IIS if ASP.NET doesn’t catch them – by tearing down the process and writing a WER report. Of course, ASP.NET catches all exceptions before letting them bubble to IIS, and logs them as request errors.  The role check uses a special codepath that appears to have this mechanism impaired.

This can be bad, if ASP.NET Roles throws an exception when connecting to the SQL roles db, any other role provider, or your custom principal throws an exception.  Instead of a request error, the w3wp.exe will crash, and eventually go into Rapid Fail Protection mode where the entire application pool will be unavailable.

So, if you are using this feature, you need to not let exceptions bubble up in the IsInRole() method.  How do you do that, if you don’t control the user principal? By creating a proxy principal, of course!

Here it is (in global.asax version):

<%@ Application Language="C#" debug="true" %>

<%@ Import Namespace="System.Security" %>

<%@ Import Namespace="System.Security.Principal" %>

<script runat="server" >

/// <summary>

/// A user principal class that handles

/// exceptions in IsInRole() method

/// </summary>

class ProxyPrincipal : IPrincipal

{

    private IPrincipal _user;

    public ProxyPrincipal(IPrincipal user)

    {

        _user = user;

    }

   

    public IIdentity Identity

    {

        get { return _user.Identity; }

    }

    public bool IsInRole(string role)

    {

        // NOTE: Instead of rethrowing the exception,

        // return false and fail the request           

        try

        {

            return _user.IsInRole(role);

        }

        catch (Exception e)

        {

            FailRequest(e);

           

            return false;

        }

    }

    private void FailRequest(Exception error)

    {

        HttpContext context = HttpContext.Current;

        if (context != null)

        {

            context.Response.StatusCode = 500;

            context.Response.Write(

                String.Format(

                    "ProxyPrincipal.IsInRole() failed with error: {0}",

                    error.Message

                    ));

            context.Response.End();

        }

    }

}

void Application_PostAuthenticateRequest(Object source, EventArgs e)

{

    HttpApplication app = (HttpApplication)source;

    HttpContext context = app.Context;

    // Set the proxy principal

    if (context.User != null)

    {

        context.User = new ProxyPrincipal(context.User);

    }

}         

</script>

You can change FailRequest() to do a Response.Redirect to an error page if you want instead of writing the exception to response.  Or just return false, causing occasional access denied errors.

This of course being the temporary solution until the bug is fixed.

As an aside, I just realized that my first post in a few months is about fixing a crash … I guess couldn't have said "I've been very busy lately" any better 🙂 I'll have to make up for it by making the next post sooner and on a more upbeat topic.

Cheers,
Mike

 

9 Comments

  1. Anonymous

    This may seem silly, but when I use UrlAuthorization in IIS 7 integrated pipeline, my rules are interpretted correctly for .aspx pages, but not for static files. For instance, I have a directory /admin which contains a default.aspx and a jscript.js file. If I put an I need to log in to see both default.aspx and jscript.js. If I put though, or , all accounts are denied access to the jscript.js file, but default.aspx is still accessible. It makes me think there is a disconnect between the ASP.NET SqlRoleProvider and the UrlAuthorization client in IIS7. Have you come across anything like this? Unfortunately, MSDN has a pitiful amount of information on this. Is there anywhere else I should look?

  2. Mike Volodarsky

    Toby,

    Be sure that Forms Authentication (if thats what you are using for authentication) is running for non-asp.net content types.

    To do this, you can set , or remove the forms authentication module and re-add it to clear its precondition.

    You should also get a FRT trace of the failed requests to diagnose the exact issue further if the above doesnt help. See http://learn.iis.net/page.aspx/266/troubleshooting-failed-requests-using-tracing-in-iis-70/.

    Thanks,
    Mike

  3. Anonymous

    Can not enable roles based ACL for static content. Works only with , putting “ and http://dl.dropbox.com/u/7307/Desktop.zip

  4. Anonymous

    Mike,

    It seems I am running into a very similar issue with a custom Role Provider. The GetRolesForUser method consistantly brings down the w3wp process when trying to instantiate a new PrinicpleContext object. Any suggestions?

  5. Anonymous

    I got lot of information from this post, I will suggest to all my friends to go this website, keep posting articles. Thanks!

  6. Anonymous

    In my case, on IIS 7 (windows server 2008) authorization executing after Application_AuthenticateRequest event and before Application_PostAuthenticateRequest.

Leave a Reply

Your email address will not be published.