In this article, we are going to learn how to Encrypting and Decrypting Query String in ASP.NET Core in simple steps.

Security is the first priority of any web application hosted on production. We use the query string to send sensitive and non-sensitive data from one page to another page. The sensitive data must be secure for doing that we need to encrypt query string. For encryption and decryption are going to use Data Protection APIs of ASP.NET Core.

Things we are going to Cover.

  1. Creating Custom Anchor tag helper for Encrypting Query string
  2. Creating Action Filter Attribute for Decrypting Query String values
  3. Custom Helper for Generating Encrypting URL in application

Getting Started with Encrypting and Decrypting Query String in ASP.NET Core

We are going to create a new application with Name WebQuery for demo as shown below.

Next, we are going to set Project Name WebQuery and location. In last part, we are going to choose .Net Core framework and ASP.NET Core Version 3.1 as the framework for application and few advance settings for such as configuring https and enabling docker we are not going to enable docker settings for this project.

Now finally click on create button to create a project.

Project structure

The project structure generated according to the configuration.

After creating project next, we are going make custom anchor tag helper which will encrypt query string values.

Creating Custom Anchor tag helper for Encrypting Query string

We are going to take values similar to anchor tag helper Text, Area, Action name, Controller, Route values.

Query string which we are going to generate we are going to add some additional parameters such as GUID and Datetime value which will make string random and will also have expiry to it such that a person cannot store this string and use it next time.

Let’s start with creating a folder with name CustomTagHelper and a class with name SecureLinkTagHelper.

After creating class, we are going to inherit it with TagHelper class. And add HtmlTargetElement Attributes.

Next, we are going to set input Attributes to this class.

using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Globalization;

namespace WebQuery.CustomTagHelper
{
    [HtmlTargetElement("a", Attributes = "s-text")]
    [HtmlTargetElement("a", Attributes = "s-areas")]
    [HtmlTargetElement("a", Attributes = "s-actionName")]
    [HtmlTargetElement("a", Attributes = "s-controllerName")]
    [HtmlTargetElement("a", Attributes = "s-rK1")]
    [HtmlTargetElement("a", Attributes = "s-rV1")]
    [HtmlTargetElement("a", Attributes = "s-rK2")]
    [HtmlTargetElement("a", Attributes = "s-rV2")]
    [HtmlTargetElement("a", Attributes = "s-rK3")]
    [HtmlTargetElement("a", Attributes = "s-rV3")]
    [HtmlTargetElement("a", Attributes = "s-rK4")]
    [HtmlTargetElement("a", Attributes = "s-rV4")]
    [HtmlTargetElement("a", Attributes = "s-rK5")]
    [HtmlTargetElement("a", Attributes = "s-rV5")]
    public class SecureLinkTagHelper : TagHelper
    {
        #region Input Attributes

        [HtmlAttributeName("s-text")]
        // ReSharper disable once InconsistentNaming
        public string text { get; set; }

        [HtmlAttributeName("s-areas")]
        // ReSharper disable once InconsistentNaming
        public string areas { get; set; } = null;

        [HtmlAttributeName("s-actionName")]
        // ReSharper disable once InconsistentNaming
        public string actionName { get; set; }

        [HtmlAttributeName("s-controllerName")]
        // ReSharper disable once InconsistentNaming
        public string controllerName { get; set; }

        [HtmlAttributeName("s-rK1")]
        // ReSharper disable once InconsistentNaming
        public string routekey1 { get; set; } = null;

        [HtmlAttributeName("s-rV1")]
        // ReSharper disable once InconsistentNaming
        public string routeValues1 { get; set; } = null;

        [HtmlAttributeName("s-rK2")]
        // ReSharper disable once InconsistentNaming
        public string routekey2 { get; set; } = null;

        [HtmlAttributeName("s-rV2")]
        // ReSharper disable once InconsistentNaming
        public string routeValues2 { get; set; } = null;

        [HtmlAttributeName("s-rK3")]
        // ReSharper disable once InconsistentNaming
        public string routekey3 { get; set; } = null;

        [HtmlAttributeName("s-rV3")]
        // ReSharper disable once InconsistentNaming
        public string routeValues3 { get; set; } = null;

        [HtmlAttributeName("s-rK4")]
        // ReSharper disable once InconsistentNaming
        public string routekey4 { get; set; } = null;

        [HtmlAttributeName("s-rV4")]
        // ReSharper disable once InconsistentNaming
        public string routeValues4 { get; set; } = null;

        [HtmlAttributeName("s-rK5")]
        // ReSharper disable once InconsistentNaming
        public string routekey5 { get; set; } = null;

        [HtmlAttributeName("s-rV5")]
        // ReSharper disable once InconsistentNaming
        public string routeValues5 { get; set; } = null;

        #endregion
    }
}

After adding Attribute next, we are going to right logic in Process Method of SecureLinkTagHelper class.

  1. Creating a Protector
  2. Creating an Anchor tag
  3. Reading Parameter Values
  4. Creating Random String
  5. Creating a Query string
  6. Setting TagHelperOutput
  public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            var dataProtectionProvider = DataProtectionProvider.Create("WebQuery");
            var protector = dataProtectionProvider.CreateProtector("WebQuery.QueryStrings");

            TagBuilder tag = new TagBuilder("a");
            string queryString = string.Empty;
            string mainString;
            IList<string> strings = new List<string>();


            if (routekey1 != null && routeValues1 != null)
            {
                strings.Add($"{routekey1}={routeValues1}");
            }

            if (routekey2 != null && routeValues2 != null)
            {
                strings.Add($"{routekey2}={routeValues2}");
            }

            if (routekey3 != null && routeValues3 != null)
            {
                strings.Add($"{routekey3}={routeValues3}");
            }

            if (routekey4 != null && routeValues4 != null)
            {
                strings.Add($"{routekey4}={routeValues4}");
            }

            if (routekey5 != null && routeValues5 != null)
            {
                strings.Add($"{routekey5}={routeValues5}");
            }

            if (routekey1 == null && routeValues1 == null
                                  && routekey2 == null && routeValues2 == null
                                  && routekey3 == null && routeValues3 == null
                                  && routekey4 == null && routeValues4 == null
                                  && routekey5 == null && routeValues5 == null)
            {
                if (String.IsNullOrEmpty(areas))
                {
                    mainString = $"/{controllerName}/{actionName}";
                }
                else
                {
                    mainString = $"/{areas}/{controllerName}/{actionName}";
                }
            }
            else
            {

                queryString += string.Join(",", strings);

                var format = new CultureInfo("en-GB");
                var random = Guid.NewGuid().ToString("N").Substring(0, 10).ToUpper();
                var values = string.Join("[", random, queryString, DateTime.Now.ToString(format));

                if (String.IsNullOrEmpty(areas))
                {
                    mainString = $"/{controllerName}/{actionName}?q={protector.Protect(values)}";
                }
                else
                {
                    mainString = $"/{areas}/{controllerName}/{actionName}?q={protector.Protect(values)}";
                }

            }

            tag.Attributes["href"] = mainString;
            tag.InnerHtml.Append(string.IsNullOrEmpty(text) ? "____" : text);
            output.Content.SetHtmlContent(tag);
        }

After creating TagHelper now let’s register it in _ViewImports.cshtml file such that we can access it in on Views in the entire application.

@using WebQuery
@using WebQuery.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, WebQuery

Now we have encrypted and registered TagHelper next we are going to decrypt it using Action Filter attribute.

Creating Action Filter Attribute for Decrypting Query String values

For creating Action Filter Attribute, we are going to make a folder first, and inside that folder, we are going to add Class with name DecryptQueryStringParameterAttribute which will inherit ActionFilterAttribute.

In this method we are going decrypt values of the query string and set it to Action method Arguments also we are going to check the timestamp of query string if it is greater than 30 min then we are going redirect it to an error page. This will allow you to protect against storing URLs.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;

namespace WebQuery.Filters
{
    public class DecryptQueryStringParameterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var dataProtectionProvider = DataProtectionProvider.Create("WebQuery");
            var protector = dataProtectionProvider.CreateProtector("WebQuery.QueryStrings");

            Dictionary<string, object> decryptedParameters = new Dictionary<string, object>();
            if (filterContext.HttpContext.Request.Query["q"].ToString() != null)
            {
                string decrptedString = protector.Unprotect(filterContext.HttpContext.Request.Query["q"].ToString());
                string[] getRandom = decrptedString.Split('[');

                var format = new CultureInfo("en-GB");
                var dateCheck = Convert.ToDateTime(getRandom[2], format);

                TimeSpan diff = Convert.ToDateTime(DateTime.Now, format) - dateCheck;

                /* For Development it is been commented */
                if (diff.Minutes > 30)
                {
                    filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { action = "Error", controller = "Error" }));
                }

                string[] paramsArrs = getRandom[1].Split(',');

                for (int i = 0; i < paramsArrs.Length; i++)
                {
                    string[] paramArr = paramsArrs[i].Split('=');
                    decryptedParameters.Add(paramArr[0], Convert.ToString(paramArr[1]));
                }
            }
            for (int i = 0; i < decryptedParameters.Count; i++)
            {
                filterContext.ActionArguments[decryptedParameters.Keys.ElementAt(i)] = decryptedParameters.Values.ElementAt(i);
            }

        }
    }
}

Now we have created attribute which will decrypt values and show in action method parameters.

Using TagHelper to Encrypt values and Decrypt values using Attribute

On Index View of Home Controller, we are going to create new Link using TagHelper which we have created.
This Custom Anchor TagHelper will Create Encrypted Link.

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-left">
    <h1 class="display-4">Welcome</h1>
    <br />
    <a s-text="Link 1" s-actionName="Profile" s-controllerName="Home" s-rK1="profileId" s-rV1="1" s-rK2="applicationId" s-rV2="MAX-1"></a>
    <br />
    <a s-text="Link 2" s-actionName="Profile" s-controllerName="Home" s-rK1="profileId" s-rV1="2" s-rK2="applicationId" s-rV2="MAX-2"></a>
    <br />
    <a s-text="Link 3" s-actionName="Profile" s-controllerName="Home" s-rK1="profileId" s-rV1="3" s-rK2="applicationId" s-rV2="MAX-3"></a>
    <br />
    <a s-text="Link 4" s-actionName="Profile" s-controllerName="Home" s-rK1="profileId" s-rV1="4" s-rK2="applicationId" s-rV2="MAX-4"></a>
    <br />
    <a s-text="Link 5" s-actionName="Profile" s-controllerName="Home" s-rK1="profileId" s-rV1="5" s-rK2="applicationId" s-rV2="MAX-5"></a>
    <br />
</div>

On click on this link, it is going to call Profile Action Method of Home Controller to decrypt values we are going to apply [DecryptQueryStringParameter] Attribute on Profile Action Method.

[DecryptQueryStringParameter]
public IActionResult Profile(string profileId, string applicationId)
{
    return View();
}

Custom Helper for Generating Encrypting URL in application

CustomQueryStringHelper is for generating URL for internal redirection because using Custom tag helper we can generating encrypted Query String on View only. In some scenario, if we want to redirect URL with Secure string, then we can use CustomQueryStringHelper.

The logic for generation helper is same as Custom tag helper.  CustomQueryStringHelper is a static class with EncryptString method. Which takes areas, actionName, controllerName and route Values as input for generating string and it will return string as Output.

This string can be passed to LocalRedirectResult to redirect.

using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Routing;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace WebQuery.Helpers
{
    public static class CustomQueryStringHelper
    {
        public static string EncryptString(string areas, string actionName, string controllerName, object routeValues)
        {
            var dataProtectionProvider = DataProtectionProvider.Create("WebQuery");
            var protector = dataProtectionProvider.CreateProtector("WebQuery.QueryStrings");

            string mainString;
            string queryString = string.Empty;
            var rvd = new RouteValueDictionary(routeValues);
            IList<string> strings = new List<string>();

            if (routeValues != null)
            {
                for (int i = 0; i < rvd.Keys.Count; i++)
                {
                    strings.Add(rvd.Keys.ElementAt(i) + "=" + rvd.Values.ElementAt(i));
                }
                queryString += string.Join(",", strings);

                var format = new CultureInfo("en-GB");
                var random = Guid.NewGuid().ToString("N").Substring(0, 10).ToUpper();
                var values = string.Join("[", random, queryString, DateTime.Now.ToString(format));

                if (String.IsNullOrEmpty(areas))
                {
                    mainString = $"/{controllerName}/{actionName}?q={protector.Protect(values)}";
                }
                else
                {
                    mainString = $"/{areas}/{controllerName}/{actionName}?q={protector.Protect(values)}";
                }

            }
            else
            {
                if (String.IsNullOrEmpty(areas))
                {
                    mainString = $"/{controllerName}/{actionName}";
                }
                else
                {
                    mainString = $"/{areas}/{controllerName}/{actionName}";
                }
            }

            return mainString;
        }
    }
}

Generating Encrypted URL and Redirecting

public IActionResult Index()
{
    var dataString = CustomQueryStringHelper.
        EncryptString("", "Profile", "Home", new { profileId = "1", applicationId = "MAX-1" });
    return LocalRedirect(dataString);
}

In this Article we have learnt how to Encrypting and Decrypting Query String in ASP.NET Core in simple steps. Hope You Like my Article Please Comment and Share if Possible.

By Saineshwar Bageri

I am Microsoft MVP | C# Corner MVP | Code Project MVP | FULL STACK .NET Developer and working on .Net Web Technology (Asp.net, Asp.net Core,.Net Core, C#, Sqlserver, MVC, Windows, Console Application, javascript, jquery, json, ORM Dapper) and also a freelance developer.