Phase 5.2: Add comprehensive logging and error handling for authentication

Middleware logging:
- Log successful authentication with cookie setting
- Log failed authentication attempts with path and remote IP
- Log token prefix (first 8 chars) for invalid tokens, never full token
- Added structured logging with ILogger<T>

Electron error handling:
- Detect 401 authentication errors in SignalR connection
- Provide helpful error message about --authtoken parameter
- Differentiate auth errors from other connection failures

Documentation:
- Added XML comments explaining security model
- Documented token generation rationale (128-bit entropy)
- Clarified middleware validation flow in comments

Security:
- Never log full token values
- Generic error messages to prevent information leakage
- Failed auth attempts logged for security monitoring
This commit is contained in:
Pierre Arnaud
2026-01-30 22:37:37 +01:00
parent 8cc3fe4fd7
commit c12a706289
3 changed files with 41 additions and 4 deletions

View File

@@ -3,27 +3,41 @@ namespace ElectronNET.AspNet.Middleware
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using ElectronNET.AspNet.Services;
/// <summary>
/// Middleware that validates authentication for all Electron requests.
/// Checks for authentication cookie or token query parameter on first request.
/// Sets HttpOnly cookie for subsequent requests.
///
/// Security Model:
/// - First request includes token as query parameter (?token=guid)
/// - Middleware validates token and sets secure HttpOnly cookie
/// - Subsequent requests use cookie (no token in URL)
/// - Both HTTP endpoints and SignalR hub protected
/// </summary>
public class ElectronAuthenticationMiddleware
{
private readonly RequestDelegate _next;
private readonly IElectronAuthenticationService _authService;
private readonly ILogger<ElectronAuthenticationMiddleware> _logger;
private const string AuthCookieName = "ElectronAuth";
public ElectronAuthenticationMiddleware(RequestDelegate next, IElectronAuthenticationService authService)
public ElectronAuthenticationMiddleware(
RequestDelegate next,
IElectronAuthenticationService authService,
ILogger<ElectronAuthenticationMiddleware> logger)
{
_next = next;
_authService = authService;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var path = context.Request.Path.Value;
// Check if authentication cookie exists
var authCookie = context.Request.Cookies[AuthCookieName];
@@ -38,8 +52,10 @@ namespace ElectronNET.AspNet.Middleware
else
{
// Invalid cookie - reject
_logger.LogWarning("Authentication failed: Invalid cookie for path {Path} from {RemoteIp}",
path, context.Connection.RemoteIpAddress);
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized: Invalid authentication cookie");
await context.Response.WriteAsync("Unauthorized: Invalid authentication");
return;
}
}
@@ -52,6 +68,8 @@ namespace ElectronNET.AspNet.Middleware
if (_authService.ValidateToken(token))
{
// Valid token - set cookie for future requests
_logger.LogInformation("Authentication successful: Setting cookie for path {Path}", path);
context.Response.Cookies.Append(AuthCookieName, token, new CookieOptions
{
HttpOnly = true, // Prevent JavaScript access (XSS protection)
@@ -64,9 +82,20 @@ namespace ElectronNET.AspNet.Middleware
await _next(context);
return;
}
else
{
// Invalid token - reject
_logger.LogWarning("Authentication failed: Invalid token (prefix: {TokenPrefix}...) for path {Path} from {RemoteIp}",
token.Length > 8 ? token.Substring(0, 8) : token, path, context.Connection.RemoteIpAddress);
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized: Invalid authentication");
return;
}
}
// Neither cookie nor valid token present - reject
_logger.LogWarning("Authentication failed: No cookie or token provided for path {Path} from {RemoteIp}",
path, context.Connection.RemoteIpAddress);
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized: Authentication required");
}

View File

@@ -113,10 +113,12 @@ namespace ElectronNET.AspNet.Runtime
private void LaunchElectron()
{
// Generate secure authentication token
// Generate secure authentication token (128-bit cryptographic random GUID)
// This token protects against unauthorized connections from other users on the same machine
this.authenticationToken = Guid.NewGuid().ToString("N"); // 32 hex chars, no hyphens
// Register token with authentication service for validation
// The middleware will validate this token on all HTTP and SignalR requests
this.authenticationService.SetExpectedToken(this.authenticationToken);
var isUnPacked = ElectronNetRuntime.StartupMethod.IsUnpackaged();

View File

@@ -78,7 +78,13 @@ class SignalRBridge {
return true;
} catch (err) {
console.error(`[SignalRBridge] Connection failed:`, err);
// Check if this is an authentication error
if (err.message && err.message.includes('401')) {
console.error(`[SignalRBridge] Authentication failed: The authentication token is invalid or missing.`);
console.error(`[SignalRBridge] Please ensure the --authtoken parameter is correctly passed to Electron.`);
} else {
console.error(`[SignalRBridge] Connection failed:`, err);
}
this.isConnected = false;
return false;
}