Gentle introduction to IDataProtector (C#)
Tags: csharp, dotnetcore, api, codenewbie
Published: 2024-03-10 19:45:20

## Introduction

[IDataProtector](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.dataprotection.idataprotector?view=aspnetcore-8.0) 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.

{% cta https://github.com/karenpayneoregon/asp-data-protection %} GitHub Source code {% endcta %}

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

```csharp
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; }
}
```

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

![index page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/14i5dxyvun736zs22s0i.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/io31kanyr700sf459th6.png)

---

**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yerfmonm1v32cf4ax9zf.png)

---

**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h4ux5y57a433pz9wg03x.png)

---

## Data protection setup

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

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

        builder.Services.AddRazorPages();

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

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.

```csharp
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);
        });
```

Then add _app.UseSession();_.

```csharp
    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();
}
```

**Handling the first button.**

```csharp
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"));

}
```

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](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.sessionextensions.setstring?view=aspnetcore-8.0) 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.

```csharp
Convert.ToInt32(Protector.Unprotect(HttpContext.Session.GetString("Id")))
```

**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.

```csharp
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 }));
    
}
```

**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.

```csharp
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() }));

}
```

---

## RazorLibrary

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


```csharp
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);
    }
}
```

## 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.

```xml
<Target Name="MakeMyDir" AfterTargets="Build">
   <MakeDir Directories="$(OutDir)LogFiles" />
</Target>
```
## 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.


{% cta https://github.com/karenpayneoregon/asp-data-protection %} GitHub Source code {% endcta %}