ASP.NET MVC Seamless Routing Encryption

There are certain pages in the application where the data passed in must be encrypted in a GET request. This could be a link redirection from an email or another site, or just an internal redirect where the parameters can’t get exposed. A link like this may actually look like:

/Some/Action?enc=345SFIPOWHG:LNNJE:LKJWET46-DSFSF:KJ

The data is nonsensical to the user, but contains identifying information about the user. For instance, the encrypted data may have the following information:

– User ID
– Response Identifier
– Response (Yes/No, Agree/Disagree, text description, whatever else)

The system can then check that the user logged in matches the user in the URL request. It can lookup the associated response database record, and record the response provided. This feature is not native to ASP.NET MVC, but is relative easy to build in. This post will illustrate how to do so, using the following steps:

1) The first step is to create an action method to define a URL. We’ll want to create two forms: one method as a redirection, and one method as an URL generation. Both of these will be extension methods on different objects:

private static string GetEncryptedData(object args)
{
    var encryption = new EncryptionService();
    var list = new RouteValueDictionary(args);
    var items = new List();

    foreach (var entry in list)
    {
         items.Add(entry.Key + "=" + (!string.IsNullOrEmpty(entry.Value) ? entry.Value : ""));
    }

    return encryption.Encrypt(String.Join("&", items));
}

public static ActionResult RedirectToActionSecure(this ControllerBase controller,
                                                  string action, 
                                                  string controller, 
                                                  object args)
{
    var encText = GetEncryptedData(args);
    return controller.RedirectToAction(action, controller, new { enc = encText });
}

public static IHtmlString ActionSecure(this UrlHelper url, 
                                       string action, 
                                       string controller, 
                                       object args)
{
    var encText = GetEncryptedData(args);
    return url.Action(action, controller, new { enc = encText });
}

Certainly, you can create overloads without the action and/or controller too, which is easy to do. Additionally, this code should do more null checking; it assumes that the data exists. The first step is get a reference to the encryption service you are using for your application. I’m leaving that out of this post. Using this service, a querystring is generated and then encrypted together. The value is stored in the “enc” querystring value when routed. This looks like in the code:

public ActionResult X()
{
   return RedirectToActionSecure("Action", "ctlr", new { a = 1, b = 2 });
}

Or in the view:

@Url.ActionSecure("Action", "ctlr", new { a = 1, b = 2 });

When a request is made to that controller and action, MVC checks for the “enc” querystring parameter. It does this in an Action filter attribute, on ActionExecuting.

public class EncryptionActionAttribute : ActionFilterAttribute
{
   public override void OnActionExecuting(ActionExecutingContext context)
   {
        var encryption = new EncryptionService();

       // Step 1: parse the URL
       var url = context.HttpContext.Request.Url.ToString();
       var index = url.IndexOf("enc=");

       if (index > 0) {
         //Get the encrypted value and decrypt it.
         var encText = context.HttpContext.Server.Decode(url.Substring(index + 4));
         var groups = encryption.Decrypt(encText).Split('&');
         var actionParams = filterContext.ActionDescriptor.GetParameters();

         //Parse all of the "KEY=VALUE" groups
         foreach (var group in groups)
         {
            var pair = group.Split('=');

               if (pair.Value == null)
                 continue;

               //Make sure the action has the parameter of the given name
               var actionParam = actionParams.FirstOrDefault(i => i.Name == pair.Key);
               if (actionParam != null) {
                  var nullType = Nullable.GetUnderlyingType(actionParam.ParameterType);

                  //If a nullable type, make sure to use changetype for that type instead; 
                  //nullable types are not supported
                  if (nullType != null)
                     context.ActionParameters[pair.Key] = 
                          Convert.ChangeType(pair.Value, nullType);
                  //Otherwise, assign and cast the value accordingly
                  else
                     context.ActionParameters[pair.Key] = 
                          Convert.ChangeType(pair.Value, actionParam.ParameterType);

               }

         }
       }
   }
}

There’s a lot to it, so let’s break it down:

– Again, missing null checks and conversion error recovery, so you’ll want to ensure that’s there.
– The first step is to grab the encrypted value from the URL and decode it; once we have this value, the encrypted value is decrypted to get the key/value pairs.
– The filter context has two parameter types: ActionDescription, which GetParameters() returns the metadata of the action method, and ActionParameters, which is a collection of values about to be injected into the action. The ActionParameters collection can be added or modified as you like.
– Each key/value pair is evaluated, and injected into the ActionParameters collection; the values that were encrypted were injected in.
– We first have to check that a nullable type is converted correctly; otherwise, the cast to a nullable type through ChangeType raises an exception.

Some/Action?enc=23442q5wetew

It’s decrypted to:

UserID: 1
ResponseID: 3
Response: YES

The values are inserted into an action method like this:

2 thoughts on “ASP.NET MVC Seamless Routing Encryption

  1. Hi, I used a similar approach here.
    Unfortunately, on IIS, I get Page not found Exception. Encryption is working but decryption is not. But on Development machine, everything works fine.

    FOr Decryption, I am using the similar approach that you did.

    Like

    • Can you add global.asax error handling in Application_Error, as there is likely an error going on? That is the best way to point out errors; it will usually provide a clear path to the problem. It could be a multitude of things, but more than likely encryption may generate a value that has a character that’s causing issues on the server? OR maybe the parameter value is being retrieved from the URL is not the exact same as the value originally sent.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s