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;
}