Token Request Validation in ASP.NET

Earlier this week during my TechDays presentation on Windows Identity Foundation, there was a part during the demo that I said would fail miserably after the user was authenticated and the token was POST’ed back to the relying party.  Out of the box, ASP.NET does request validation.  If a user has submitted content through request parameters it goes through a validation step, and by default this step is to break on anything funky such as angle brackets.  This helps to deter things like cross site scripting attacks.  However, we were passing XML so we needed to turn off this validation.  There are two approaches to doing this.

The first approach, which is what I did in the demo, was to set the validation mode to “2.0”.  All this did was tell ASP.NET to use a less strict validation scheme.  To do that you need to add a line to the web.config file:

<system.web>
<httpRuntime requestValidationMode=”2.0” />
</system.web>

This is not the best way to do things though.  It creates a new vector for attack, as you’ve just allowed an endpoint to accept trivial data.  What is more preferred is to create a custom request validator.  You can find a great example in the Fabrikam Shipping demo.

It’s pretty straightforward to create a validator.  First you create a class that inherits System.Web.Util.RequestValidator, and then you override the method IsValidRequestString(…).  At that point you can do anything you want to validate, but the demo code tries to build a SignInResponseMessage object from the wresult parameter.  If it creates the object successfully the request is valid.  Otherwise it passes the request to the base implementation of IsValidRequestString(…).

The code to handle this validation is pretty straightforward:

    public class WSFederationRequestValidator : RequestValidator
    {
        protected override bool IsValidRequestString(HttpContext context,
            string value, RequestValidationSource requestValidationSource, 
            string collectionKey, out int validationFailureIndex)
        {
            validationFailureIndex = 0;

            if (requestValidationSource == RequestValidationSource.Form
                && collectionKey.Equals(WSFederationConstants.Parameters.Result, 
                   StringComparison.Ordinal))
            {
                SignInResponseMessage message =
                     WSFederationMessage.CreateFromFormPost(context.Request) 
                     as SignInResponseMessage;

                if (message != null)
                {
                    return true;
                }
            }

            return base.IsValidRequestString(context, value, requestValidationSource,
                   collectionKey, out validationFailureIndex);
        }
    }

Once you’ve created your request validator, you need to update the web.config file to tell .NET to use the validator.  You can do that by adding the following xml:

<system.web>
<httpRuntime requestValidationType="Microsoft.Samples.DPE.FabrikamShipping.Web.Security.WSFederationRequestValidator" />
</system.web>

You can find the validation code in FabrikamShipping.Web\Security\WSFederationRequestValidator.cs within the FabrikamShipping solution.

Using the ASP.NET Roles Provider with Windows Identity Foundation

Using the Windows Identity Foundation to handle user authentication and identity management can require you to drastically rethink how you will build your application.  There are a few fundamental differences between how authentication and roles will be handled when you switch to a Claims model. 

As an example if you used an STS to provide Claims to your application, you wouldn’t (couldn’t really) use the FormsAuthentication class.

Another thing to keep in mind is how you would handle Roles.  WIF sort of handles roles if you were to use <location> tags in web.config files like:

  <location path="test.aspx">
    <system.web>
      <authorization>
        <deny users="*" />
        <allow roles="admin" />
      </authorization>
    </system.web>
  </location>

WIF would handle this in an earlier part of the page lifecycle, and only allow authenticated users with a returned Role claim of admin.  This works well for some cases, but not all.

In larger applications we may want custom Roles, and the ability to map these roles to the Roles provided by the STS. 

This is by no means a place to tell you when you should use what architectural design, but a lot of times we want somewhere in the middle of these extremes…

Sometimes we just want to use the Roles class to check for role membership based on the Role claims.  From what I can find there is no RolesProvider implementation for WIF, so I wrote a very simple provider.  It is by all rights a hack.  The reason I say this is because there are quite a few methods that just can’t be implemented.  For instance, getting roles for other users is impossible, or adding a user to a role, or creating a role, deleting a role, etc.  This is all impossible because we can’t send anything back to the STS telling it what to do with the roles.

We are also limited to the scope of the roles.  I can only get the roles of the currently logged in user, nothing beyond.  So, with all the usual warnings (it works on my machine, don’t blame me if it steals your soul, etc)…

using System;
using System.Linq;
using System.Threading;
using System.Web.Security;
using Microsoft.IdentityModel.Claims;

public class ClaimsRoleProvider : RoleProvider
{
    IClaimsIdentity claimsIdentity;
    ClaimCollection userClaims;

    private void initClaims()
    {
        claimsIdentity = ((IClaimsPrincipal)(Thread.CurrentPrincipal)).Identities[0];
        userClaims = claimsIdentity.Claims;
    }

    public override string ApplicationName
    {
        get
        {
            initClaims();
            return claimsIdentity.GetType().ToString();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public override bool RoleExists(string roleName)
    {
        initClaims();

        return userClaims.Where(r => r.Value == roleName).Any();
    }

    public override bool IsUserInRole(string username, string roleName)
    {
        initClaims();

        return userClaims.Where(r => r.Value == roleName).Any();
    }

    public override string[] GetRolesForUser(string username)
    {
        initClaims();

        return userClaims.Where(r => r.ClaimType == ClaimTypes.Role).Select(r => r.Value).ToArray();
    }

    public override string[] GetAllRoles()
    {
        initClaims();

        return userClaims.Where(r => r.ClaimType == ClaimTypes.Role).Select(r => r.Value).ToArray();
    }

    #region Not implementable

    public override string[] GetUsersInRole(string roleName)
    {
        throw new NotImplementedException();
    }

    public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
    {
        throw new NotImplementedException();
    }

    public override void CreateRole(string roleName)
    {
        throw new NotImplementedException();
    }

    public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
    {
        throw new NotImplementedException();
    }

    public override string[] FindUsersInRole(string roleName, string usernameToMatch)
    {
        throw new NotImplementedException();
    }

    public override void AddUsersToRoles(string[] usernames, string[] roleNames)
    {
        throw new NotImplementedException();
    }

    #endregion
}

The next step is to modify the web.config to use this provider.  I put this in a separate assembly so it could be re-used.

    <roleManager enabled="true" defaultProvider="claimsRoleProvider">
      <providers>
        <clear />
        <add name="claimsRoleProvider" type="ClaimsRoleProvider, MyAssem.Providers,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=4a27739ef3347280" />
      </providers>
    </roleManager>

One final thing to be aware of… Roles.IsUserInRole(string roleName) uses IPrincipal.Identity.Name in it’s overloaded version in lieu of a username parameter which could result in this ArgumentNullException:

Value cannot be null.
Parameter name: username

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: username
Source Error:

Line 17:         var claims = from c in claimsIdentity.Claims select c;
Line 18: 
Line 19:         bool inRole = Roles.IsUserInRole("VPN");
Line 20:         
Line 21:         foreach (var r in claims)

Since the IClaimsIdentity is getting generated based on the claims it receives, it sets the Name property to whatever claim value is associated with the http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name claim type. If one isn't present, it will be set to null.

It took way too long for me to figure that one out. :)

C# Dynamic Type Conversions

I’ve been looking at ways of parsing types and values from text without having to do switch/case statements or explicit casting.  So far, based on my understanding of statically typed languages, is that this is impossible with a statically typed language.

<Question> Is this really true?</Question>

Given my current knowledge, my way of bypassing this is to use the new dynamic type in .NET 4.  It allows me to implicitly assign an object without having to cast it.  It works by bypassing the type checking at compile time.

Here’s a fairly straightforward example:

static void Main(string[] args)
{
	Type boolType = Type.GetType("System.Boolean");
	Console.WriteLine(!parse("true", boolType));

	Type dateTimeType = Type.GetType("System.DateTime");

	DateTime date = parse("7/7/2010", dateTimeType);
	Console.WriteLine(date.AddDays(1));

	Console.ReadLine();
}

static dynamic parse(string value, Type t)
{
	return Convert.ChangeType(value, t);
}

Now, if I were to do something crazy and call

DateTime someDate = parse(“1234”, Type.GetType(“System.Int32”));

a RuntimeBinderException would be thrown because you cannot implicitly convert between an int and a DateTime.

It certainly makes things a little easier.