Quick Tip: ASP.NET Core – IDataProtectionProvider

THIS BLOG MAY CONTAIN AFFILIATE LINKS, MEANING I GET A COMMISSION IF YOU DECIDE TO MAKE A PURCHASE THROUGH MY LINKS, AT NO COST TO YOU. THIS HELPS ME KEEP RUNNING THIS BLOG. THANK YOU!

Recently, I have been working on one of my side projects and there was a need to send my users an email, containing some temporary URL, which contains some identifier numbers in the query string, which is then used to query other stuff within the database, so there is a potential for enumeration vulnerability.

The URL I’m sending out in the email looks very similar to the following:

/projectId=120&userId=789

It’s not the same URL that I have in my project, but just to get an idea. Imagine the ProjectId and the UserId exist in the database as the real identifying numbers, and you don’t want to give away this kind of information. You need to convert those Ids into some other values, and de-convert them once received on the server.

I didn’t want to implement something myself to kind of encrypt those numbers before I send them out in an email, and then decrypt them on the other side. Instead, I used something that is available in ASP.NET Core out of the box. It was the IDataProtectionProvider interface. You can read more about this interface and its usage in the official documentation, but I wasn’t aware of this interface before, so thanks for the LinkedIn Learning platform for providing plenty of information for ASP.NET (Core) developers.

As I mentioned before, I needed to “encrypt” some temporary query string, which the IDataProtectionProvider is good for. By default, it can “remember” how to decrypt the stuff for 90 days, but it’s configurable, so you can use shorter periods if needed.

So, how do you use the IDataProtectionProvider?

Well, in my case, because I’m building an MVC project, I simply injected the interface as a dependency into the controller and that’s it. Registering it in the IoC isn’t needed as it is available for use straight away.

For the sake of this short quick tip article, I’ve created a demo. Here’s some code to look at.

        private static readonly User[] _users =
        {
            new User { Id = 1, Name = "Sarah Phillips" },
            new User { Id = 2, Name = "James McVoy" },
            new User { Id = 3, Name = "Bob Lambo" },
            new User { Id = 4, Name = "Matt Delonge" },
            new User { Id = 5, Name = "Sue West" }
        };

        private User GetUser(int id)
        {
            return _users.FirstOrDefault(user => user.Id == id);
        }

To make the demo easier to navigate, I’ve placed the code within the same controller class. Here’s how the User class looks like:

    public class User
    {
        public int Id { get; set; }

        public string Name { get; set; }
    }

Controller’s constructor code:

        private readonly IDataProtector dataProtector;

        public HomeController(IDataProtectionProvider dataProtectionProvider)
        {
            dataProtector = dataProtectionProvider.CreateProtector(purpose: "UserIdProtection");
        }

And finally, here’s how I used the IDataProtectionProvider, this is the Index() method within the HomeController:

        public IActionResult Index()
        {
            // Get a random user
            var user = GetUser(1);

            ViewData["PlainUserId"] = user.Id;
            ViewData["UserName"] = user.Name;
            ViewData["EncryptedUserId"] = dataProtector.Protect(user.Id.ToString());

            return View();
        }

What this code renders when you run the application is this page:

Now, if you clicked on the link ‘View user’, you would be taken onto another method, the ViewUser(string id) method, where it decrypts the user Id value to the original integer value:

        public IActionResult ViewUser(string id)
        {
            // Decrypt user Id
            var decryptedUserId = Convert.ToInt32(dataProtector.Unprotect(id));

            // Get the user
            var user = GetUser(decryptedUserId);

            ViewData["PlainUserId"] = user.Id;
            ViewData["UserName"] = user.Name;

            return View();
        }

…and the page generated is this (pay attention to the URL generated):

So the userId is now hidden from the outside world, and no one knows what the real UserId value is in the database. If someone would try to tamper with the URL, they would get the following (well, it would be error-handled, but):

That’s it for this quick tip article, hope this was useful for you, and if you’re like me willing to learn something new every day, and would like to learn more of these nitty-gritty bits available in ASP.NET Core, I would recommend you to get a LinkedIn Learning sub for yourself as well.

All the code above is available on Github.