1+ namespace ElectronNET . AspNet . Middleware
2+ {
3+ using System ;
4+ using System . Threading . Tasks ;
5+ using Microsoft . AspNetCore . Http ;
6+ using Microsoft . Extensions . Logging ;
7+ using ElectronNET . AspNet . Services ;
8+
9+ /// <summary>
10+ /// Middleware that validates authentication for all Electron requests.
11+ /// Checks for authentication cookie or token query parameter on first request.
12+ /// Sets HttpOnly cookie for subsequent requests.
13+ ///
14+ /// Security Model:
15+ /// - First request includes token as query parameter (?token=guid)
16+ /// - Middleware validates token and sets secure HttpOnly cookie
17+ /// - Subsequent requests use cookie (no token in URL)
18+ /// - HTTP endpoints protected
19+ /// </summary>
20+ public class ElectronAuthenticationMiddleware
21+ {
22+ private readonly RequestDelegate _next ;
23+ private readonly IElectronAuthenticationService _authService ;
24+ private readonly ILogger < ElectronAuthenticationMiddleware > _logger ;
25+ private const string AuthCookieName = "ElectronAuth" ;
26+
27+ public ElectronAuthenticationMiddleware (
28+ RequestDelegate next ,
29+ IElectronAuthenticationService authService ,
30+ ILogger < ElectronAuthenticationMiddleware > logger )
31+ {
32+ _next = next ;
33+ _authService = authService ;
34+ _logger = logger ;
35+ }
36+
37+ public async Task InvokeAsync ( HttpContext context )
38+ {
39+ var path = context . Request . Path . Value ;
40+
41+ // Check if authentication cookie exists
42+ var authCookie = context . Request . Cookies [ AuthCookieName ] ;
43+
44+ if ( ! string . IsNullOrEmpty ( authCookie ) )
45+ {
46+ // Cookie present - validate it
47+ if ( _authService . ValidateToken ( authCookie ) )
48+ {
49+ await _next ( context ) ;
50+ return ;
51+ }
52+ else
53+ {
54+ // Invalid cookie - reject
55+ _logger . LogWarning ( "Authentication failed: Invalid cookie for path {Path} from {RemoteIp}" , path , context . Connection . RemoteIpAddress ) ;
56+ context . Response . StatusCode = 401 ;
57+ await context . Response . WriteAsync ( "Unauthorized: Invalid authentication" ) ;
58+ return ;
59+ }
60+ }
61+
62+ // No cookie - check for token in query string (first-time authentication)
63+ var token = context . Request . Query [ "token" ] . ToString ( ) ;
64+
65+ if ( ! string . IsNullOrEmpty ( token ) )
66+ {
67+ if ( _authService . ValidateToken ( token ) )
68+ {
69+ // Valid token - set cookie for future requests
70+ _logger . LogInformation ( "Authentication successful: Setting cookie for path {Path}" , path ) ;
71+
72+ context . Response . Cookies . Append ( AuthCookieName , token , new CookieOptions
73+ {
74+ HttpOnly = true , // Prevent JavaScript access (XSS protection)
75+ SameSite = SameSiteMode . Strict , // CSRF protection
76+ Path = "/" , // Valid for all routes
77+ Secure = false , // False because localhost is HTTP
78+ IsEssential = true // Required for app to function
79+ } ) ;
80+
81+ await _next ( context ) ;
82+ return ;
83+ }
84+ else
85+ {
86+ // Invalid token - reject
87+ _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 ) ;
88+ context . Response . StatusCode = 401 ;
89+ await context . Response . WriteAsync ( "Unauthorized: Invalid authentication" ) ;
90+ return ;
91+ }
92+ }
93+
94+ // Neither cookie nor valid token present - reject
95+ _logger . LogWarning ( "Authentication failed: No cookie or token provided for path {Path} from {RemoteIp}" , path , context . Connection . RemoteIpAddress ) ;
96+ context . Response . StatusCode = 401 ;
97+ await context . Response . WriteAsync ( "Unauthorized: Authentication required" ) ;
98+ }
99+ }
100+ }
0 commit comments