EV.DataProtection
8.0.0
dotnet add package EV.DataProtection --version 8.0.0
NuGet\Install-Package EV.DataProtection -Version 8.0.0
<PackageReference Include="EV.DataProtection" Version="8.0.0" />
<PackageVersion Include="EV.DataProtection" Version="8.0.0" />
<PackageReference Include="EV.DataProtection" />
paket add EV.DataProtection --version 8.0.0
#r "nuget: EV.DataProtection, 8.0.0"
#:package EV.DataProtection@8.0.0
#addin nuget:?package=EV.DataProtection&version=8.0.0
#tool nuget:?package=EV.DataProtection&version=8.0.0
EV.DataProtection
Librería de Protección de Datos con Expiración Temporal: Una librería simple y segura para .NET 8 que gestiona la protección de datos sensibles usando ASP.NET Data Protection API con soporte para expiración temporal y validación automática.
Características
- Integración Fácil: Configuración simple con inyección de dependencias
- Protección con Expiración: Soporte para cifrado de datos con tiempo de vida configurable
- Validación Integrada: Validación automática de configuración con mensajes de error claros
- Logging Comprehensivo: Logging detallado para operaciones de protección/desprotección
- Manejo de Errores: Gestión elegante de errores con resultados estructurados
- Listo para Producción: Manejo seguro de datos sensibles con configuración robusta
Instalación
dotnet add package EV.DataProtection
Inicio Rápido
1. Configuración (appsettings.json)
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"EV.DataProtection": "Debug"
}
},
"AllowedHosts": "*",
"DataProtectionConfiguration": {
"Enabled": true,
"DataProtectionRedirectUrl": "https://yourdomain.com",
"DataProtectionSecret": "your-secure-secret-key-minimum-20-characters",
"DataProtectionTimeLifeDays": 15
}
}
2. Registro de Servicios (Program.cs)
using EV.DataProtection;
var builder = WebApplication.CreateBuilder(args);
// Agregar servicios al contenedor
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Registrar ASP.NET Data Protection
builder.Services.AddDataProtection();
// Registrar el servicio de EV.DataProtection
builder.Services.AddDataProtection(builder.Configuration);
var app = builder.Build();
// Configurar el pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
3. Uso en Servicios
using EV.DataProtection.Interfaces;
using EV.DataProtection.Models;
public class TokenService
{
private readonly IDataProtectionService _dataProtection;
private readonly ILogger<TokenService> _logger;
public TokenService(IDataProtectionService dataProtection, ILogger<TokenService> logger)
{
_dataProtection = dataProtection;
_logger = logger;
}
public string? CreateSecureToken(string userData)
{
// Proteger datos con expiración (usa configuración por defecto)
return _dataProtection.ProtectToTimeLimited(userData);
}
public string? CreateCustomExpirationToken(string userData, double daysToExpire)
{
// Proteger datos con expiración personalizada
return _dataProtection.ProtectToTimeLimited(userData, daysToExpire);
}
public (bool IsValid, string? Data) ValidateToken(string encryptedToken)
{
var result = _dataProtection.UnprotectToTimeLimited(encryptedToken);
return (result.IsSuccess, result.Message);
}
public string? ProtectSimpleData(string data)
{
// Protección simple sin expiración
return _dataProtection.Protect(data);
}
public string? UnprotectSimpleData(string encryptedData)
{
try
{
return _dataProtection.Unprotect(encryptedData);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error unprotecting simple data");
return null;
}
}
}
Opciones de Configuración
Propiedades de DataProtectionConfiguration
Propiedad | Tipo | Por Defecto | Descripción |
---|---|---|---|
Enabled |
bool | true | Habilitar/deshabilitar protección globalmente |
DataProtectionSecret |
string | "" | Clave secreta para protección (mín. 20 caracteres) |
DataProtectionRedirectUrl |
string | "" | URL de redirección para usar en frontend para derificar algún registro de usuario |
DataProtectionTimeLifeDays |
int | 1 | Días de vida por defecto para datos protegidos |
Validaciones de Configuración
- DataProtectionSecret: Requerido, entre 20-256 caracteres, solo caracteres alfanuméricos y especiales
- DataProtectionRedirectUrl: Requerido, debe ser una URL válida
- DataProtectionTimeLifeDays: Debe ser mayor a 0
Ejemplos de Configuración
Ambiente de Desarrollo
{
"DataProtectionConfiguration": {
"Enabled": true,
"DataProtectionRedirectUrl": "https://localhost:5001",
"DataProtectionSecret": "dev-secret-key-for-testing-minimum-20-chars",
"DataProtectionTimeLifeDays": 1
}
}
Ambiente de Producción
{
"DataProtectionConfiguration": {
"Enabled": true,
"DataProtectionRedirectUrl": "https://yourdomain.com",
"DataProtectionSecret": "pro-secret-key-for-testing-minimum-20-chars",
"DataProtectionTimeLifeDays": 15
}
}
Protección Deshabilitada (para testing)
{
"DataProtectionConfiguration": {
"Enabled": false,
"DataProtectionRedirectUrl": "https://localhost:5001",
"DataProtectionSecret": "test-secret-for-disabled-mode",
"DataProtectionTimeLifeDays": 1
}
}
Referencia de API
IDataProtectionService
Método | Descripción | Parámetros |
---|---|---|
Protect(plainText) |
Protege texto plano sin expiración | string plainText |
Unprotect(cipherText) |
Desprotege texto cifrado | string cipherText |
ProtectToTimeLimited(plainText, timeLifeDays?) |
Protege con expiración temporal | string plainText, double timeLifeDays = 0 |
UnprotectToTimeLimited(cipherText) |
Desprotege datos con validación de expiración | string cipherText |
PayloadResult
Propiedad | Tipo | Descripción |
---|---|---|
IsSuccess |
bool | Indica si la operación fue exitosa |
Message |
string? | Datos desprotegidos o mensaje de error |
Patrones de Uso
Operaciones Básicas de Protección
// Proteger datos simples (sin expiración)
var protected = _dataProtection.Protect("sensitive data");
// Desproteger datos simples
var unprotected = _dataProtection.Unprotect(protected);
// Proteger con expiración (usa configuración por defecto)
var timedProtected = _dataProtection.ProtectToTimeLimited("sensitive data");
// Proteger con expiración personalizada (7 días)
var customProtected = _dataProtection.ProtectToTimeLimited("sensitive data", 7);
// Desproteger con validación de expiración
var result = _dataProtection.UnprotectToTimeLimited(timedProtected);
if (result.IsSuccess)
{
Console.WriteLine($"Data: {result.Message}");
}
else
{
Console.WriteLine($"Error: {result.Message}");
}
Uso Avanzado
Implementación en Controladores
using EV.DataProtection.Api.Requests;
using EV.DataProtection.Interfaces;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
[ApiController]
[Route("api/[controller]")]
public class AuthController(IDataProtectionService dataProtection, ILogger<AuthController> logger) : ControllerBase
{
[HttpPost("generate-token")]
public ActionResult<string> GenerateToken([FromBody] TokenRequest request)
{
try
{
var data = JsonSerializer.Serialize(new
{
request.UserId,
request.Email
});
var token = dataProtection.ProtectToTimeLimited(data);
if (token == null)
{
return BadRequest("Failed to generate token");
}
return Ok(new { Token = token });
}
catch (Exception ex)
{
logger.LogError(ex, "Error generating token for user {UserId}", request.UserId);
return StatusCode(500, "Internal server error");
}
}
[HttpPost("validate-token")]
public ActionResult<object> ValidateToken([FromBody] string token)
{
try
{
var result = dataProtection.UnprotectToTimeLimited(token);
if (!result.IsSuccess)
{
return BadRequest(new { Error = result.Data });
}
var data = JsonSerializer.Deserialize<dynamic>(result.Data!);
return Ok(new { Valid = true, Data = data });
}
catch (Exception ex)
{
logger.LogError(ex, "Error validating token");
return StatusCode(500, "Internal server error");
}
}
}
Servicio de Gestión de Sesiones
public class SessionService : ISessionService
{
private readonly IDataProtectionService _dataProtection;
private readonly ILogger<SessionService> _logger;
public SessionService(IDataProtectionService dataProtection, ILogger<SessionService> logger)
{
_dataProtection = dataProtection;
_logger = logger;
}
public string? CreateSession(UserSession session)
{
try
{
var sessionData = JsonSerializer.Serialize(session);
return _dataProtection.ProtectToTimeLimited(sessionData, session.ExpirationDays);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating session for user {UserId}", session.UserId);
return null;
}
}
public (bool IsValid, UserSession? Session) ValidateSession(string sessionToken)
{
try
{
var result = _dataProtection.UnprotectToTimeLimited(sessionToken);
if (!result.IsSuccess)
{
_logger.LogWarning("Invalid session token: {Error}", result.Message);
return (false, null);
}
var session = JsonSerializer.Deserialize<UserSession>(result.Message!);
return (true, session);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error validating session token");
return (false, null);
}
}
public string? RefreshSession(string currentToken, double newExpirationDays)
{
var (isValid, session) = ValidateSession(currentToken);
if (!isValid || session == null)
{
return null;
}
// Crear nueva sesión con mismos datos pero nueva expiración
session.ExpirationDays = newExpirationDays;
session.LastRefresh = DateTime.UtcNow;
return CreateSession(session);
}
}
public class UserSession
{
public int UserId { get; set; }
public string Email { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime LastRefresh { get; set; } = DateTime.UtcNow;
public double ExpirationDays { get; set; } = 1;
}
public interface ISessionService
{
string? CreateSession(UserSession session);
(bool IsValid, UserSession? Session) ValidateSession(string sessionToken);
string? RefreshSession(string currentToken, double newExpirationDays);
}
Servicio de Protección de URLs
public class SecureUrlService : ISecureUrlService
{
private readonly IDataProtectionService _dataProtection;
private readonly DataProtectionConfiguration _config;
private readonly ILogger<SecureUrlService> _logger;
public SecureUrlService(
IDataProtectionService dataProtection,
IOptions<DataProtectionConfiguration> config,
ILogger<SecureUrlService> logger)
{
_dataProtection = dataProtection;
_config = config.Value;
_logger = logger;
}
public string? CreateSecureUrl(string baseUrl, Dictionary<string, string> parameters, double? expirationDays = null)
{
try
{
var urlData = new SecureUrlData
{
BaseUrl = baseUrl,
Parameters = parameters,
CreatedAt = DateTime.UtcNow
};
var serializedData = JsonSerializer.Serialize(urlData);
var protectedData = _dataProtection.ProtectToTimeLimited(serializedData, expirationDays ?? 0);
if (protectedData == null)
{
return null;
}
return $"{_config.DataProtectionRedirectUrl}?token={Uri.EscapeDataString(protectedData)}";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating secure URL for {BaseUrl}", baseUrl);
return null;
}
}
public (bool IsValid, string? RedirectUrl) ValidateAndGetRedirectUrl(string token)
{
try
{
var result = _dataProtection.UnprotectToTimeLimited(token);
if (!result.IsSuccess)
{
return (false, null);
}
var urlData = JsonSerializer.Deserialize<SecureUrlData>(result.Message!);
if (urlData == null)
{
return (false, null);
}
// Construir URL con parámetros
var uriBuilder = new UriBuilder(urlData.BaseUrl);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
foreach (var param in urlData.Parameters)
{
query[param.Key] = param.Value;
}
uriBuilder.Query = query.ToString();
return (true, uriBuilder.ToString());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error validating secure URL token");
return (false, null);
}
}
}
public class SecureUrlData
{
public string BaseUrl { get; set; } = string.Empty;
public Dictionary<string, string> Parameters { get; set; } = new();
public DateTime CreatedAt { get; set; }
}
public interface ISecureUrlService
{
string? CreateSecureUrl(string baseUrl, Dictionary<string, string> parameters, double? expirationDays = null);
(bool IsValid, string? RedirectUrl) ValidateAndGetRedirectUrl(string token);
}
Métodos Alternativos de Registro
Configuración mediante Delegate
// Configurar opciones programáticamente
builder.Services.AddDataProtectionService(options =>
{
options.Enabled = true;
options.DataProtectionSecret = "programmatic-secret-key-minimum-20-characters";
options.DataProtectionRedirectUrl = "https://yourdomain.com";
options.DataProtectionTimeLifeDays = 30;
});
Configuración desde Sección Específica
// Usar un nombre de sección de configuración diferente
var protectionSection = builder.Configuration.GetSection("DataProtectionConfiguration");
builder.Services.AddDataProtectionService(protectionSection);
Registro Manual Completo
// Registro manual para control total
builder.Services.Configure<DataProtectionConfiguration>(
builder.Configuration.GetSection(DataProtectionConfiguration.SectionName));
builder.Services.AddScoped<IDataProtectionService, DataProtectionService>();
Manejo de Errores
La librería maneja errores de forma elegante:
- Protección Deshabilitada: Cuando
Enabled = false
, todas las operaciones retornan null - Operaciones Protect: Retornan null si hay error y loguean la excepción
- Operaciones Unprotect: Retornan PayloadResult con IsSuccess = false y mensaje de error
- Validación: La configuración es validada al inicio con mensajes descriptivos
// Las operaciones de protección son seguras incluso si hay errores
var protected = _dataProtection.ProtectToTimeLimited("data"); // Retorna null si hay error
var result = _dataProtection.UnprotectToTimeLimited("invalid_token");
if (!result.IsSuccess)
{
Console.WriteLine($"Error: {result.Message}"); // "Token has expired" o mensaje de excepción
}
Mejores Prácticas
1. Generar Secretos Seguros
Usar secretos únicos y complejos para cada ambiente:
// ✅ Bueno - Secreto único, complejo, 64+ caracteres
"PRO-S9Duu*N8JxshfW%hLG@PSuvSjx##%43E#r2bG^?#N-p#=tuHvp*S5AXmSW*9&HJ8"
// ❌ Malo - Muy simple o predecible
"my-secret-key-123"
"password123456789"
2. Manejar Resultados de Desprotección
Siempre verificar IsSuccess al desproteger datos:
var result = _dataProtection.UnprotectToTimeLimited(token);
if (result.IsSuccess)
{
// Procesar datos válidos
ProcessData(result.Message);
}
else
{
// Manejar error (token expirado, inválido, etc.)
LogError(result.Message);
return Unauthorized();
}
3. Usar Tiempos de Expiración Apropiados
Balance entre seguridad y experiencia de usuario:
// Tokens de sesión - corta duración
var sessionToken = _dataProtection.ProtectToTimeLimited(sessionData, 1); // 1 día
// URLs de verificación - duración media
var verificationUrl = _dataProtection.ProtectToTimeLimited(urlData, 7); // 1 semana
// Datos de configuración - larga duración
var configData = _dataProtection.ProtectToTimeLimited(config, 30); // 1 mes
4. Logging y Monitoreo
Configurar logging apropiado para auditoría:
// En appsettings.json
{
"Logging": {
"LogLevel": {
"EV.DataProtection": "Information", // Para producción
"EV.DataProtection": "Debug" // Para desarrollo
}
}
}
5. Validación de Entrada
Validar datos antes de proteger:
public string? CreateUserToken(string userData)
{
if (string.IsNullOrWhiteSpace(userData))
{
_logger.LogWarning("Attempted to protect empty user data");
return null;
}
if (userData.Length > 1000) // Límite de tamaño
{
_logger.LogWarning("User data too large for protection: {Size} characters", userData.Length);
return null;
}
return _dataProtection.ProtectToTimeLimited(userData);
}
6. Configuración por Ambiente
Usar diferentes configuraciones según el ambiente:
// Development: expiración corta para testing rápido
// Staging: configuración similar a producción
// Production: expiración balanceada y secretos seguros
var timeLifeDays = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") switch
{
"Development" => 0.1, // ~2.4 horas
"Staging" => 7, // 1 semana
"Production" => 15, // 2 semanas
_ => 1 // Por defecto
};
Solución de Problemas
Problemas Comunes
"Token has expired": El token ha superado su tiempo de vida configurado
"Invalid payload": El token está corrupto o no es válido
"DataProtectionSecret is required": Falta configurar el secreto o es muy corto
"The key {guid} was not found": Problema con claves de ASP.NET Data Protection (reiniciar aplicación)
Logging
Habilitar logging de debug para solucionar problemas:
{
"Logging": {
"LogLevel": {
"EV.DataProtection": "Debug"
}
}
}
Validación de Configuración
// Verificar configuración al inicio
public static void ValidateDataProtection(IServiceProvider services)
{
using var scope = services.CreateScope();
var dataProtection = scope.ServiceProvider.GetRequiredService<IDataProtectionService>();
// Prueba básica
var testData = "test-data";
var protected = dataProtection.ProtectToTimeLimited(testData, 1);
if (protected != null)
{
var result = dataProtection.UnprotectToTimeLimited(protected);
if (result.IsSuccess && result.Message == testData)
{
Console.WriteLine("✅ Data Protection working correctly");
}
else
{
Console.WriteLine("❌ Data Protection validation failed");
}
}
else
{
Console.WriteLine("❌ Data Protection configuration issue");
}
}
Depuración de Tokens
public class DataProtectionDebugService
{
private readonly IDataProtectionService _dataProtection;
public void AnalyzeToken(string token)
{
var result = _dataProtection.UnprotectToTimeLimited(token);
Console.WriteLine($"Token Analysis:");
Console.WriteLine($"- Length: {token.Length} characters");
Console.WriteLine($"- Valid: {result.IsSuccess}");
Console.WriteLine($"- Message: {result.Message}");
if (result.IsSuccess)
{
// Analizar payload si es JSON
try
{
var parsed = JsonSerializer.Deserialize<JsonElement>(result.Message!);
Console.WriteLine($"- JSON Structure: {parsed}");
}
catch
{
Console.WriteLine($"- Raw Data: {result.Message}");
}
}
}
}
Requisitos
- .NET 8.0 o superior
- Microsoft.AspNetCore.DataProtection 8.0+
- Microsoft.Extensions.Configuration 8.0+
- Microsoft.Extensions.DependencyInjection 8.0+
- Microsoft.Extensions.Logging 8.0+
- Microsoft.Extensions.Options 8.0+
- System.Text.Json 8.0+
Licencia
Copyright © 2025 EV. Todos los derechos reservados.
Protección de Datos Segura y Temporal para .NET 8
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.AspNetCore.DataProtection (>= 8.0.20)
- Microsoft.Extensions.Configuration (>= 8.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection (>= 8.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Logging (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
8.0.0 | 297 | 9/21/2025 |
🎉 Lanzamiento Inicial 8.0.0
✨ Características de Data Protection:
• Configuración simple con inyección de dependencias
• Protección con expiración configurable
• Manejo elegante de errores
• Logging comprehensivo