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:
@@ -319,6 +319,9 @@ try
|
|||||||
builder.Services.AddScoped<SignatureCacheService>();
|
builder.Services.AddScoped<SignatureCacheService>();
|
||||||
builder.Services.AddSingleton<AppVersionService>();
|
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)
|
// DevExpress Server-Side Services (CRITICAL for DxPdfViewer)
|
||||||
builder.Services.AddDevExpressBlazor();
|
builder.Services.AddDevExpressBlazor();
|
||||||
builder.Services.AddDevExpressServerSideBlazorPdfViewer();
|
builder.Services.AddDevExpressServerSideBlazorPdfViewer();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user