@Blog.Author(Nandip Makwana) .LearningExperience(ASP.NET, ASP.NET MVC, IIS, jQuery & Technology Surrounding it...)

September 12, 2017 comment , , ,

ASP.NET Core MVC Value Provider for Encrypted Route Parameter

Back in January 2013 & March 2013, I had published a detailed articles on this blog on how to create a custom Value Provider for ASP.NET MVC and ASP.NET 4.5 Model Binding respectively. In normal scenario it is not required to create custom Value Provider but sometime we require to process incoming request parameter values before default value providers feed it to model binder. For e.g. in an ecommerce application encrypted invoice id is passed in as a query string parameter or route parameter so that end-user can see and download invoice without login but as invoice id is encrypted they cannot manipulate it by guessing other possible value. In such case, we can access this encrypted query string or route parameter value from within MVC action method and decrypt it there and utilize decrypted values. This is fairly straightforward for relatively small application with only a handful of MVC actions accepting encrypted values. But consider a use case where you have plenty of action methods which need to accept encrypted values either passed as a query string or route parameter. Here instead of decrypting it manually from within each and every MVC actions, we can create custom Value Provider (I’ve named it as CryptoValueProvider) to do the job. CryptoValueProvider should read encrypted query string or route parameter, decrypt it and feed plain decrypted value to default model binder so that we can get direct value in action parameter. Here in this article, we will see how to create custom CryptoValueProvider in ASP.NET Core.

Before we go into nitty-gritty of how CryptoValueProvider is built, here is the quick link to GitHub repo with sample project and link to how to get started with example.

When I started building CryptoValueProvider, I have considered three situations which is

  1. Existing default value providers and model binder should not be affected or break in any way
  2. CryptoValueProvider should support action method with all parameters as encrypted
[CryptoValueProvider]
public IActionResult Example1(int param1, string param2)
{
}

In above Example1 action, both parameter param1 & param2 values are passed as a encrypted and it is decrypted by CryptoValueProvider on the fly right before model binding happen.

  1. CryptoValueProvider should support action method with only some of the parameters as encrypted
public IActionResult Example2([FromCrypto]int secretPersonId, [FromCrypto]string secretParam2, int visibleParam1, Person person)
{
}

Here in Example2, we can see that I have not marked method with CryptoValueProvider attribute instead I have marked only secretPersonId & secretParam2 with FromCrypto attribute. FromCrypto is a binding source metadata object representing a source of data for model binding and it is similar to FromBody, FromQuery, etc. FromCrypto essentially direct the ASP.NET Core MVC that this particular parameter must be bound through CryptoValueProvider and not other value providers even though it is passed as query string, request body, form body, etc. just ignore the other value providers.

Now that we have discussed what we are aiming to achieve with CryptoValueProvider, we will start looking into actual code.

CryptoBindingSource.cs

public class CryptoBindingSource
{
    public static readonly BindingSource Crypto = new BindingSource(
                "Crypto",
                "BindingSource_Crypto",
                isGreedy: false,
                isFromRequest: true);
}

CryptoBindingSource class has read only object of BindingSource named as Crypto. We will require this Crypto object while creating CryptoValueProvider and FromCrypto parameter binding attribute.

CryptoParamsProtector.cs

Next required class is the one which enable us to encrypt parameters dictionary into single string which we can pass as a route parameter and decrypt it back to parameter dictionary.

public class CryptoParamsProtector
{
    IDataProtector _protector;
 
    public CryptoParamsProtector(IDataProtectionProvider dataProtectionProvider)
    {
        _protector = dataProtectionProvider.CreateProtector(GetType().FullName);
    }
 
    public string EncryptParamDictionary(Dictionary<string, string> parameters)
    {
        var paramsInSingleString = string.Join("+", parameters
            .Select(p => string.Format("{0}={1}", p.Key.ToLower(), p.Value)));
        return _protector.Protect(paramsInSingleString);
    }
 
    public Dictionary<string, string> DecryptToParamDictionary(string encryptedParameters)
    {
        var paramsInSingleString = string.Empty;
        try
        {
            paramsInSingleString = _protector.Unprotect(encryptedParameters);
        }
        catch
        {
            //return empty dictionary when string encryptedParameters is not protected with _protector
            return new Dictionary<string, string>();
        }
        return paramsInSingleString.Split('+')
            .Select(p => p.Split('='))
            .ToDictionary(p => p[0], p => p[1]);
    }
}

As we can see that I have used ASP.NET Core Data Protection API for the encryption/decryption purpose. More information on Data Protection API is available here.

CryptoValueProvider.cs

This is actual implementation of CryptoValueProvider. We need to implement IValueProvider interface to create custom value provider but as we also want to support parameter level binding through FromCrypto attribute (implementation later in this article) hence we inherited it from BindingSourceValueProvider abstract class which can filter value provider based on binding source metadata of parameter.

public class CryptoValueProvider : BindingSourceValueProvider
{
    string _encryptedParameters;
    CryptoParamsProtector _protector;
    Dictionary<string, string> _values;
 
    public CryptoValueProvider(BindingSource bindingSource, CryptoParamsProtector protector, string encryptedParameters)
        : base(bindingSource)
    {
        _encryptedParameters = encryptedParameters;
        _protector = protector;
    }
 
    public override bool ContainsPrefix(string prefix)
    {
        if (_values == null)
        {
            if (string.IsNullOrEmpty(_encryptedParameters))
            {
                _values = new Dictionary<string, string>();
            }
            else
            {
                _values = _protector.DecryptToParamDictionary(_encryptedParameters);
            }
        }
 
        return _values.ContainsKey(prefix.ToLower());
    }
 
    public override ValueProviderResult GetValue(string key)
    {
        if (_values.ContainsKey(key.ToLower()))
        {
            return new ValueProviderResult(new StringValues(_values[key.ToLower()]));
        }
        else
        {
            return ValueProviderResult.None;
        }
    }
}

If we look at the third parameter of constructor, it is encrypted string. We are decrypting it with the help of CryptoParamsProtector object passed in as second parameter of constructor and storing it as dictionary. GetValue method will return plain decrypted value when invoked by model binder right before it bind the model. Hence we do not require to decrypt it from within action methods.

CryptoValueProviderFactory.cs

CryptoValueProvider is ready to instantiate and plug it into ASP.NET Core model binding pipeline. And it is where CryptoValueProviderFactory comes in picture.

   1: public class CryptoValueProviderFactory : IValueProviderFactory
   2: {
   3:     public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
   4:     {
   5:         var paramsProtector = (CryptoParamsProtector)context.ActionContext.HttpContext
   6:             .RequestServices.GetService(typeof(CryptoParamsProtector));
   7:  
   8:         context.ValueProviders.Add(new CryptoValueProvider(CryptoBindingSource.Crypto
   9:             , paramsProtector
  10:             , context.ActionContext.RouteData.Values["id"]?.ToString()));
  11:  
  12:         return Task.CompletedTask;
  13:     }
  14: }

Here when we are adding CryptoValueProvider to context at line no 8, we are passing 3 parameters as

  1. CryptoBindingSource.Crypto – this is the same read only binding source object which we created as first step in this article. This Crypto binding source object indicate that only CryptoValueProvider can act as an data source for parameter with FromCrypto attribute.
  2. paramsProtector – it is instance of CryptoParamsProtector used for encryption/decryption.
  3. Routedata.Values["id"] – here we are passing encrypted string as route parameter id hence we are reading encrypted string from route parameter id and passing it to value provider.

Below is the code of ConfigureServices of Startup.cs file. We are adding CryptoValueProviderFactory at the end of existing value provider factory list hence giving default value provider a higher preference. We have also added scoped service for CryptoParamsProtector so that we can inject it in controller or through instance of IServiceProvider.

   1: public void ConfigureServices(IServiceCollection services)
   2: {
   3:     services.AddScoped(typeof(CryptoParamsProtector));
   4:  
   5:     services.AddMvc(mvcOptions =>
   6:     {
   7:         mvcOptions.ValueProviderFactories.Add(new CryptoValueProviderFactory());
   8:     });
   9: }

We have configured CryptoValueProvider during application startup in ConfigureService. Next step is to create attribute to mark action method or method parameter.

CryptoValueProviderAttribute.cs

By using CryptoValueProviderAttribute we can denote that all the parameters of this action method can accept only encrypted values from route parameter or query string.

[AttributeUsage(AttributeTargets.Method)]
public class CryptoValueProviderAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
 
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.ValueProviderFactories.Clear();
        context.ValueProviderFactories.Add(new CryptoValueProviderFactory());
    }
}

Here we are implementing IResourceFilter interface which allow us to implement code that can be executed just right before and immediate after model binding. Purpose of this attribute is to indicate that all the parameters are accepting only encrypted values hence we do not require default value providers and hence we are removing it from list and then adding back only CryptoValueProvider. By this way we can ensure that for this particular request processing only CryptoValueProvider is used and not other.

FromCryptoAttribute.cs

FromCrypto attribute is used at parameter level when data source of some of the action parameters are other than CryptoValueProvider.

[AttributeUsage(AttributeTargets.Parameter)]
public class FromCryptoAttribute : Attribute, IBindingSourceMetadata
{
    public BindingSource BindingSource => CryptoBindingSource.Crypto;
}

This attribute implements IBindingSourceMetadata interface, which is used to specify data source for model binding. Here we are using same CryptoBindingSource.Crypto object which we have used in value provider factory and subsequently it is passed to BindingSourceValueProvider constructor so that it can filter CryptoValueProvider in conjunction with IBindingSourceMetadata or FromCrypto attribute here.

Examples

I have uploaded sample project on my GitHub repo here. You can clone it and run it with Visual Studio 2017 and ASP.NET Core 2.0 installed. Here is a link to example to get started with. Below is the quick simple usage. For more example please visit example page.

  1. Create action method and mark it with CryptoValueProvider attribute or mark some of the parameter with FromCrypto attribute
[CryptoValueProvider]
public IActionResult Example1(int param1, string param2)
{
}
/*OR*/
public IActionResult Example2([FromCrypto]int param1, string param2, [FromCrypto]string param3)
{
}
  1. Create parameter dictionary and encrypt it with CryptoParamsProtector
public class HomeController : Controller
{
    CryptoParamsProtector _protector;
 
    public HomeController(CryptoParamsProtector protector)
    {
        _protector = protector;
    }
 
    public IActionResult Index()
    {
        var paramDictionary = new Dictionary();
        paramDictionary.Add("param1", 1234.ToString());
        paramDictionary.Add("param2", "Hello World!");
        ViewBag.encryptedRouteParam1 = _protector.EncryptParamDictionary(paramDictionary);
 
        return View();
    }
}
  1. Use encrypted string as route parameter to generate link
<a asp-controller="demo" asp-action="example1" asp-route-id="@ViewBag.encryptedRouteParam1"><h4>Example 1 Demo</h4></a>
comments powered by Disqus

Featured Content

Resources & Tools

About Nandip Makwana

Nandip Makwana is passionate about digital world and web. He completed his Masters in Computer Application in June 2011. Currently he is working as a Software Engineer. He has shown great promise and command over ASP.NET and technologies surrounding it during his academic years and professorial life...continue reading