Files
jdescopingtool/PLANS/2026-01-03-encrypted-login-design.md
T
Joseph Doherty ec4c8fab87 refactor: relocate options classes to dedicated Options folders
Move configuration options from Core/DataAccess/DataSync/ExcelIO to
dedicated Options folders within each project for better organization.
Update all references and tests accordingly.
2026-01-03 08:55:08 -05:00

3.7 KiB

Encrypted Login Design

Overview

Consolidate login models into Core project and implement RSA-OAEP encryption for login credentials between Blazor WASM client and API.

Decisions

Decision Choice Rationale
Encryption algorithm RSA-OAEP (SHA-256) Simple direct encryption, suitable for small payloads
Key size 2048-bit Industry standard, ~20 year security horizon
Key distribution Fetch at runtime Allows key rotation without client redeployment
Key storage Auto-generate, persist to file Self-bootstrapping, no manual key management
Key expiration None Internal enterprise app, manual rotation if needed

New Models (Core)

All in JdeScoping.Core/Models/Auth/:

// LoginModel.cs - shared form/request model
public class LoginModel
{
    [Required(ErrorMessage = "Username is required")]
    public string Username { get; set; } = string.Empty;

    [Required(ErrorMessage = "Password is required")]
    public string Password { get; set; } = string.Empty;
}

// LoginResultModel.cs - API response (unencrypted)
public record LoginResultModel(
    bool Success,
    string? ErrorMessage,
    UserInfo? User);

// EncryptedLoginRequest.cs - encrypted payload wrapper
public record EncryptedLoginRequest(string EncryptedData);

// PublicKeyResponse.cs - public key endpoint response
public record PublicKeyResponse(string PublicKeyPem);

RSA Key Service (Infrastructure)

Interface: JdeScoping.Core/Interfaces/IRsaKeyService.cs

public interface IRsaKeyService
{
    string GetPublicKeyPem();
    byte[] Decrypt(byte[] ciphertext);
}

Implementation: JdeScoping.Infrastructure/Security/RsaKeyService.cs

  • Auto-generates 2048-bit RSA key on first startup if not exists
  • Persists private key to configurable file path
  • Exports public key as PEM format
  • Decrypts using RSA-OAEP with SHA-256 padding

API Changes

New endpoint:

GET /api/auth/public-key → PublicKeyResponse

Modified endpoint:

POST /api/auth/login
Body: EncryptedLoginRequest { EncryptedData: base64 }
Response: LoginResultModel

Flow:

  1. Receive base64-encoded encrypted data
  2. Decode and decrypt with RSA private key
  3. Deserialize JSON to LoginModel
  4. Authenticate via IAuthService
  5. Return LoginResultModel (unencrypted)

Client Changes

New service: ICryptoService / CryptoService

  • Uses Blazor.SubtleCrypto NuGet package
  • Fetches and caches server public key
  • Encrypts LoginModel JSON with RSA-OAEP
  • Returns base64-encoded ciphertext

Modified AuthService:

  • Injects ICryptoService
  • Encrypts LoginModel before sending
  • Sends EncryptedLoginRequest to API

Files to Delete

  • JdeScoping.Api/Models/LoginRequest.cs
  • JdeScoping.Client/Models/LoginModel.cs

Dependencies

JdeScoping.Client.csproj:

<PackageReference Include="Blazor.SubtleCrypto" Version="..." />

DI Registration

API/Host:

services.AddSingleton<IRsaKeyService>(sp =>
    new RsaKeyService(Path.Combine(appDataPath, "rsa-key.bin")));

Client:

services.AddScoped<ICryptoService, CryptoService>();

Test Coverage

RsaKeyServiceTests (Api.Tests)

  • GetPublicKeyPem returns valid PEM format
  • Decrypt with valid ciphertext returns plaintext
  • Constructor loads existing key from file
  • Constructor generates and persists new key if missing

CryptoServiceTests (Client.Tests)

  • EncryptLoginAsync returns valid base64
  • EncryptLoginAsync caches public key (single HTTP call)

Integration (optional)

  • Full roundtrip: encrypt on client → decrypt on API → authenticate