mirror of
https://github.com/ElectronNET/Electron.NET.git
synced 2026-09-25 08:25:04 +00:00
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:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user