DEV Community

Cover image for Gentle introduction to IDataProtector (C#)
Karen Payne
Karen Payne

Posted on • Updated on

Gentle introduction to IDataProtector (C#)

Introduction

IDataProtector is an interface that provides data protection services. Learn how to use the interface IDataProtector to encrypt and decrypt route parameters along with hiding route parameters.

GitHub Source code

The following model is used to read and update a SQL-Server table using EF Core.

public partial class UserLogin
{
    public int Id { get; set; }

    [NotMapped]
    [BindProperty]
    public string EncryptedId { get; set; }

    [Display(Name = "Email")]
    [Required]
    public string EmailAddress { get; set; }

    [DataType(DataType.Text)]
    [Required]
    public string Pin { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

The index page presents a list of users by email address.

index page

The dropdown has users read from a database table via EF Core.

Buttons

First button passes the id of the select to an edit page using data protection and hides the id in the browsers address bar.

button one


Second button passes the id of the select to an edit page using data protection revealing the id encrypted browsers address bar along with passing bogus rather than id.

shows encrypted route parameter


Third button passes the id of the select to an edit page without data protection revealing the actual value and uses bogus rather than id so that a hacker has no idea what the parameter is for.

shows actual route parameter in address bar


Data protection setup

To configure, using dependency injection, in Program.cs, add AddDataProtection as shown below.

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        builder.Services.AddRazorPages();

        builder.Services.AddDataProtection()
            .SetDefaultKeyLifetime(TimeSpan.FromDays(7));
Enter fullscreen mode Exit fullscreen mode

Create a variable of for IDataProtector then pass IDataProtectionProvider in the page constructor and finally initialize Protector field.

Session setup

In Program.cs, use _AddSession _to setup for sessions.

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        builder.Services.AddRazorPages();

        builder.Services.AddDataProtection()
            .SetDefaultKeyLifetime(TimeSpan.FromDays(7));

        builder.Services.AddSession(options => {
            options.IdleTimeout = TimeSpan.FromMinutes(30);
        });
Enter fullscreen mode Exit fullscreen mode

Then add app.UseSession();.

    var app = builder.Build();

    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseSession();

    app.UseRouting();

    app.UseAuthorization();

    app.MapRazorPages();

    app.Run();
}
Enter fullscreen mode Exit fullscreen mode

Handling the first button.

public Task<IActionResult> OnPostHiddenExample()
{

    UserLogin userLogin = _context.UserLogin.FirstOrDefault(x => x.Id == Identifier)!;

    if (userLogin == null)
    {
        return Task.FromResult<IActionResult>(Page());
    }

    EncryptedId = Protector.Protect(Identifier.ToString()!);
    HttpContext.Session.SetString("Id", EncryptedId);

    return Task.FromResult<IActionResult>(RedirectToPage("./Edit"));

}
Enter fullscreen mode Exit fullscreen mode

Using EF Core, read the selected user where Identifier has the id from the dropdown, if for some reason the user does not exists return back to the same page.

Encrypt the id then HttpContext.Session.SetString to store the id to a session variable.

Finally pass change to the edit page without a route parameter. In the edit page, get the id by unprotecting the session variable.

Convert.ToInt32(Protector.Unprotect(HttpContext.Session.GetString("Id")))
Enter fullscreen mode Exit fullscreen mode

Note In the edit page there is a check to see if there is a value in Temp data, this is for the third button which does not protect the route parameter.

Handling the second button.

Everything is the same as the first button except in this case the route parameter is passed and seen in the browser address bar, encrypted.

public Task<IActionResult> OnPostVisibleExample()
{

    UserLogin userLogin = _context.UserLogin.FirstOrDefault(x => x.Id == Identifier)!;

    if (userLogin == null)
    {
        return Task.FromResult<IActionResult>(Page());
    }

    EncryptedId = Protector.Protect(Identifier.ToString()!);
    HttpContext.Session.SetString("Id", EncryptedId);

    return Task.FromResult<IActionResult>(
        RedirectToPage("./Edit", new { bogus = EncryptedId }));

}
Enter fullscreen mode Exit fullscreen mode

Handling the third button.

There is no data protection, this is how a route parameter is normally done.

The Temp data is used to provide a switch in the edit page to determine if a route parameter is passed without data protection.

public Task<IActionResult> OnPostNormalExample()
{

    UserLogin userLogin = _context.UserLogin.FirstOrDefault(x => x.Id == Identifier)!;

    if (userLogin == null)
    {
        return Task.FromResult<IActionResult>(Page());
    }

    /*
     * This needs to be done because out of three possible post
     * two are protected while one is not protected. normal, normal
     * has zero significant meaning other than an identifier.
     */
    TempData.Put("normal", "normal");

    /*
     * bogus is meant to throw off hackers rather than using id.
     */
    return Task.FromResult<IActionResult>(
        RedirectToPage("./Edit", new { bogus = Identifier!.Value.ToString() }));

}
Enter fullscreen mode Exit fullscreen mode

RazorLibrary

Contains two methods, one to set Temp data and one to get Temp data.

public static class TempDataHelper
{
    /// <summary>
    /// Put an item into TempData
    /// </summary>
    /// <typeparam name="T">Item type</typeparam>
    /// <param name="sender">TempData</param>
    /// <param name="key">Used to retrieve value with <see cref="Get{T}"/> </param>
    /// <param name="value">Value to store</param>
    public static void Put<T>(this ITempDataDictionary sender, string key, T value) where T : class
    {
        sender[key] = JsonSerializer.Serialize(value);
    }
    /// <summary>
    /// Get value by key in TempData
    /// </summary>
    /// <typeparam name="T">Item type</typeparam>
    /// <param name="sender">TempData</param>
    /// <param name="key">Used to retrieve value</param>
    /// <returns>Item</returns>
    public static T Get<T>(this ITempDataDictionary sender, string key) where T : class
    {
        sender.TryGetValue(key, out var unknown);
        return unknown == null ? null : JsonSerializer.Deserialize<T>((string)unknown);
    }
}
Enter fullscreen mode Exit fullscreen mode

EF Core logging

Logs to a text file under the application folder, LogFiles with a class included for logging to a file, DbContextToFileLogger.

On the first build of the project the folder LogFiles is created via the following MS-Build command in the project file.

<Target Name="MakeMyDir" AfterTargets="Build">
   <MakeDir Directories="$(OutDir)LogFiles" />
</Target>
Enter fullscreen mode Exit fullscreen mode

Summary

In this article techniques have been presented to allow route parameters passed to other pages is protected using Microsoft’s data protection interface which for some will assist against hackers or that the data simply should not be seen.

GitHub Source code

Top comments (3)

Collapse
 
jangelodev profile image
João Angelo

Hi Karen Payne,
Your tips are very useful
Thanks for sharing

Collapse
 
karenpayneoregon profile image
Karen Payne

Your welcome.

Collapse
 
djgalvan profile image
David Galvan

Hey Karen, thank you for sharing this bit on data protection, I'll have it in mind when I go digging!