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.
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; }
}
The index page presents a list of users by email address.
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.
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.
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.
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));
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);
});
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();
}
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"));
}
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")))
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 }));
}
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() }));
}
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);
}
}
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>
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.
Top comments (3)
Hi Karen Payne,
Your tips are very useful
Thanks for sharing
Your welcome.
Hey Karen, thank you for sharing this bit on data protection, I'll have it in mind when I go digging!