From c12a70628907bc2f4c83349d3172dbf2cbec74de Mon Sep 17 00:00:00 2001 From: Pierre Arnaud Date: Fri, 30 Jan 2026 22:37:37 +0100 Subject: [PATCH] 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 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 --- .../ElectronAuthenticationMiddleware.cs | 33 +++++++++++++++++-- ...ntimeControllerAspNetDotnetFirstSignalR.cs | 4 ++- src/ElectronNET.Host/api/signalr-bridge.js | 8 ++++- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/ElectronNET.AspNet/Middleware/ElectronAuthenticationMiddleware.cs b/src/ElectronNET.AspNet/Middleware/ElectronAuthenticationMiddleware.cs index 9bb94e0..e93b837 100644 --- a/src/ElectronNET.AspNet/Middleware/ElectronAuthenticationMiddleware.cs +++ b/src/ElectronNET.AspNet/Middleware/ElectronAuthenticationMiddleware.cs @@ -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; /// /// 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 /// public class ElectronAuthenticationMiddleware { private readonly RequestDelegate _next; private readonly IElectronAuthenticationService _authService; + private readonly ILogger _logger; private const string AuthCookieName = "ElectronAuth"; - public ElectronAuthenticationMiddleware(RequestDelegate next, IElectronAuthenticationService authService) + public ElectronAuthenticationMiddleware( + RequestDelegate next, + IElectronAuthenticationService authService, + ILogger 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"); } diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirstSignalR.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirstSignalR.cs index 5c0f067..343c0ac 100644 --- a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirstSignalR.cs +++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirstSignalR.cs @@ -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(); diff --git a/src/ElectronNET.Host/api/signalr-bridge.js b/src/ElectronNET.Host/api/signalr-bridge.js index 828cbfb..6987e08 100644 --- a/src/ElectronNET.Host/api/signalr-bridge.js +++ b/src/ElectronNET.Host/api/signalr-bridge.js @@ -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; }