Add EnvelopeAuthService for SSR authentication

Introduced `EnvelopeAuthService` and `IEnvelopeAuthService` to handle server-side authentication for envelope receiver pages.

- Registered `IEnvelopeAuthService` as a scoped service in `Program.cs`.
- Implemented `EnvelopeAuthService` to validate user authentication and envelope key matching using `IHttpContextAccessor` and JWT claims.
- Added methods to retrieve the authenticated envelope key and current user (`ClaimsPrincipal`).
- Prioritized `NameIdentifier` claim for envelope key extraction, with fallback to `sub` claim.
- Documented the service and interface with XML comments for clarity.

This centralizes authentication logic, ensuring reusability and adherence to SSR best practices.
This commit is contained in:
2026-06-24 15:57:06 +02:00
parent 9947774ba8
commit ed17852542
3 changed files with 123 additions and 0 deletions

View File

@@ -319,6 +319,9 @@ try
builder.Services.AddScoped<SignatureCacheService>();
builder.Services.AddSingleton<AppVersionService>();
// SSR Authentication Service (for Envelope Receiver pages)
builder.Services.AddScoped<EnvelopeGenerator.Server.Services.IEnvelopeAuthService, EnvelopeGenerator.Server.Services.EnvelopeAuthService>();
// DevExpress Server-Side Services (CRITICAL for DxPdfViewer)
builder.Services.AddDevExpressBlazor();
builder.Services.AddDevExpressServerSideBlazorPdfViewer();

View File

@@ -0,0 +1,91 @@
using System.Security.Claims;
namespace EnvelopeGenerator.Server.Services;
/// <summary>
/// Server-side authentication service for envelope receiver access validation.
/// Uses HttpContext to check JWT claims and envelope key authorization.
/// </summary>
public class EnvelopeAuthService : IEnvelopeAuthService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<EnvelopeAuthService> _logger;
public EnvelopeAuthService(
IHttpContextAccessor httpContextAccessor,
ILogger<EnvelopeAuthService> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
/// <inheritdoc/>
public bool IsAuthenticated(string envelopeKey)
{
if (string.IsNullOrWhiteSpace(envelopeKey))
{
_logger.LogWarning("IsAuthenticated called with null or empty envelope key");
return false;
}
var context = _httpContextAccessor.HttpContext;
// Check if user is authenticated
if (context?.User?.Identity?.IsAuthenticated != true)
{
_logger.LogDebug("User is not authenticated for envelope {EnvelopeKey}", envelopeKey);
return false;
}
// Get envelope key from claims
var sub = GetEnvelopeKeyFromClaims(context.User);
// Verify envelope key matches
var isValid = sub == envelopeKey;
if (!isValid)
{
_logger.LogWarning(
"Envelope key mismatch: Expected {ExpectedKey}, Got {ActualKey}",
envelopeKey,
sub ?? "(null)");
}
else
{
_logger.LogDebug("User authenticated for envelope {EnvelopeKey}", envelopeKey);
}
return isValid;
}
/// <inheritdoc/>
public string? GetAuthenticatedEnvelopeKey()
{
var context = _httpContextAccessor.HttpContext;
if (context?.User?.Identity?.IsAuthenticated != true)
return null;
return GetEnvelopeKeyFromClaims(context.User);
}
/// <inheritdoc/>
public ClaimsPrincipal? GetCurrentUser()
{
return _httpContextAccessor.HttpContext?.User;
}
private string? GetEnvelopeKeyFromClaims(ClaimsPrincipal user)
{
// Try NameIdentifier first (standard claim)
var sub = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
// Fallback to "sub" claim (JWT standard)
if (string.IsNullOrWhiteSpace(sub))
{
sub = user.FindFirst("sub")?.Value;
}
return sub;
}
}

View File

@@ -0,0 +1,29 @@
using System.Security.Claims;
namespace EnvelopeGenerator.Server.Services;
/// <summary>
/// Service for handling envelope-specific authentication in SSR (Server-Side Rendering) context.
/// </summary>
public interface IEnvelopeAuthService
{
/// <summary>
/// Checks if the current user is authenticated for the given envelope key.
/// Validates both that the user is authenticated AND that the envelope key matches their claims.
/// </summary>
/// <param name="envelopeKey">The envelope key to validate against user claims.</param>
/// <returns>True if user is authenticated and envelope key matches; otherwise false.</returns>
bool IsAuthenticated(string envelopeKey);
/// <summary>
/// Gets the authenticated envelope key from the current user's claims (NameIdentifier or "sub" claim).
/// </summary>
/// <returns>The envelope key if user is authenticated; otherwise null.</returns>
string? GetAuthenticatedEnvelopeKey();
/// <summary>
/// Gets the current HttpContext user principal.
/// </summary>
/// <returns>ClaimsPrincipal if available; otherwise null.</returns>
ClaimsPrincipal? GetCurrentUser();
}