EV.DataProtection 8.0.0

dotnet add package EV.DataProtection --version 8.0.0
                    
NuGet\Install-Package EV.DataProtection -Version 8.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EV.DataProtection" Version="8.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EV.DataProtection" Version="8.0.0" />
                    
Directory.Packages.props
<PackageReference Include="EV.DataProtection" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EV.DataProtection --version 8.0.0
                    
#r "nuget: EV.DataProtection, 8.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EV.DataProtection@8.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EV.DataProtection&version=8.0.0
                    
Install as a Cake Addin
#tool nuget:?package=EV.DataProtection&version=8.0.0
                    
Install as a Cake Tool

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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