1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Threading . Tasks ;
4+ using Microsoft . AspNetCore . Http ;
5+ using Microsoft . Extensions . Logging ;
6+ using Microsoft . Extensions . Options ;
7+
8+ namespace MyApp ;
9+
10+ /// <summary>
11+ /// Options for configuring the UserAgentBlockingMiddleware
12+ /// </summary>
13+ public class UserAgentBlockingOptions
14+ {
15+ /// <summary>
16+ /// List of user agents to block (supports exact matches or substring matches)
17+ /// </summary>
18+ public List < string > BlockedUserAgents { get ; set ; } = new ( ) ;
19+
20+ /// <summary>
21+ /// HTTP status code to return when a user agent is blocked (defaults to 403 Forbidden)
22+ /// </summary>
23+ public int BlockedStatusCode { get ; set ; } = StatusCodes . Status403Forbidden ;
24+
25+ /// <summary>
26+ /// Optional message to return in the response body when a user agent is blocked
27+ /// </summary>
28+ public string BlockedMessage { get ; set ; } = "Access denied based on your user agent" ;
29+
30+ /// <summary>
31+ /// If true, will perform case-insensitive matching (defaults to true)
32+ /// </summary>
33+ public bool IgnoreCase { get ; set ; } = true ;
34+
35+ /// <summary>
36+ /// If true, blocked requests will be logged (defaults to true)
37+ /// </summary>
38+ public bool LogBlockedRequests { get ; set ; } = true ;
39+ }
40+
41+ /// <summary>
42+ /// Middleware that blocks requests from specific user agents
43+ /// </summary>
44+ public class UserAgentBlockingMiddleware
45+ {
46+ private readonly RequestDelegate _next ;
47+ private readonly UserAgentBlockingOptions _options ;
48+ private readonly ILogger < UserAgentBlockingMiddleware > _logger ;
49+
50+ public UserAgentBlockingMiddleware (
51+ RequestDelegate next ,
52+ IOptions < UserAgentBlockingOptions > options ,
53+ ILogger < UserAgentBlockingMiddleware > logger )
54+ {
55+ _next = next ?? throw new ArgumentNullException ( nameof ( next ) ) ;
56+ _options = options ? . Value ?? throw new ArgumentNullException ( nameof ( options ) ) ;
57+ _logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
58+ }
59+
60+ public async Task InvokeAsync ( HttpContext context )
61+ {
62+ if ( context == null )
63+ {
64+ throw new ArgumentNullException ( nameof ( context ) ) ;
65+ }
66+
67+ // Get the User-Agent header
68+ string userAgent = context . Request . Headers [ "User-Agent" ] . ToString ( ) ;
69+
70+ // Check if the user agent should be blocked
71+ if ( ShouldBlockUserAgent ( userAgent ) )
72+ {
73+ // Log the blocked request if enabled
74+ if ( _options . LogBlockedRequests )
75+ {
76+ _logger . LogInformation (
77+ "Request blocked from user agent: {UserAgent}, IP: {IPAddress}, Path: {Path}" ,
78+ userAgent ,
79+ context . Connection . RemoteIpAddress ,
80+ context . Request . Path ) ;
81+ }
82+
83+ // Set the response status code
84+ context . Response . StatusCode = _options . BlockedStatusCode ;
85+ context . Response . ContentType = "text/plain" ;
86+
87+ // Write the blocked message to the response
88+ await context . Response . WriteAsync ( _options . BlockedMessage ) ;
89+ return ;
90+ }
91+
92+ // If not blocked, continue to the next middleware
93+ await _next ( context ) ;
94+ }
95+
96+ private bool ShouldBlockUserAgent ( string userAgent )
97+ {
98+ if ( string . IsNullOrEmpty ( userAgent ) )
99+ {
100+ // You might want to block requests with no user agent
101+ // Return true here if you want to block empty user agents
102+ return false ;
103+ }
104+
105+ foreach ( var blockedAgent in _options . BlockedUserAgents )
106+ {
107+ var comparison = _options . IgnoreCase
108+ ? StringComparison . OrdinalIgnoreCase
109+ : StringComparison . Ordinal ;
110+
111+ if ( userAgent . Contains ( blockedAgent , comparison ) )
112+ return true ;
113+ if ( blockedAgent . Contains ( ' ' ) && userAgent . Contains ( blockedAgent . Replace ( " " , "" ) , comparison ) )
114+ return true ;
115+ }
116+
117+ return false ;
118+ }
119+ }
120+
121+ /// <summary>
122+ /// Extension methods for registering the UserAgentBlockingMiddleware
123+ /// </summary>
124+ public static class UserAgentBlockingMiddlewareExtensions
125+ {
126+ /// <summary>
127+ /// Adds the UserAgentBlockingMiddleware to the application pipeline
128+ /// </summary>
129+ public static IApplicationBuilder UseUserAgentBlocking (
130+ this IApplicationBuilder builder )
131+ {
132+ return builder . UseMiddleware < UserAgentBlockingMiddleware > ( ) ;
133+ }
134+
135+ /// <summary>
136+ /// Adds the UserAgentBlockingMiddleware to the application pipeline with custom options
137+ /// </summary>
138+ public static IApplicationBuilder UseUserAgentBlocking (
139+ this IApplicationBuilder builder ,
140+ Action < UserAgentBlockingOptions > configureOptions )
141+ {
142+ // Create a new options instance
143+ var options = new UserAgentBlockingOptions ( ) ;
144+
145+ // Apply the configuration
146+ configureOptions ( options ) ;
147+
148+ // Use the middleware with the configured options
149+ return builder . UseMiddleware < UserAgentBlockingMiddleware > ( Options . Create ( options ) ) ;
150+ }
151+ }
0 commit comments