isIPsafe
C# and .NET logo

C# and .NET

Check and report with HttpClient, and block in ASP.NET Core middleware.

HttpClient and System.Net.Http.Json are all you need. The ASP.NET Core middleware below refuses listed addresses on the paths you choose, with an hour of caching.

A small client

using System.Net.Http.Headers;
using System.Net.Http.Json;

public sealed record CheckData(int AbuseConfidenceScore, int TotalReports, string? CountryCode);
public sealed record CheckResponse(CheckData Data);

public sealed class IsIPsafeClient(HttpClient http)
{
    // Register with: builder.Services.AddHttpClient<IsIPsafeClient>(c => {
    //     c.BaseAddress = new Uri("https://isipsafe.com/");
    //     c.Timeout = TimeSpan.FromSeconds(3);
    //     c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", builder.Configuration["IsIPsafe:Key"]);
    // });

    public async Task<int> ScoreAsync(string ip, CancellationToken ct = default)
    {
        try
        {
            var res = await http.GetFromJsonAsync<CheckResponse>($"api/v1/check?ip={Uri.EscapeDataString(ip)}", ct);
            return res?.Data.AbuseConfidenceScore ?? 0;
        }
        catch (Exception e) when (e is HttpRequestException or TaskCanceledException)
        {
            return 0; // fail open
        }
    }

    public async Task ReportAsync(string ip, string categories, string comment, CancellationToken ct = default)
    {
        using var body = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["ip"] = ip, ["categories"] = categories, ["comment"] = comment,
        });
        using var res = await http.PostAsync("api/v1/report", body, ct); // 429: already reported recently
    }
}

ASP.NET Core middleware

// Program.cs, after builder.Services.AddMemoryCache() and the AddHttpClient call above
using Microsoft.Extensions.Caching.Memory;   // at the top of the file

app.Use(async (ctx, next) =>
{
    var ip = ctx.Connection.RemoteIpAddress?.ToString();
    if (ip is not null && ctx.Request.Path.StartsWithSegments("/account/login"))
    {
        var cache = ctx.RequestServices.GetRequiredService<IMemoryCache>();
        var client = ctx.RequestServices.GetRequiredService<IsIPsafeClient>();
        var score = await cache.GetOrCreateAsync($"isipsafe:{ip}", e =>
        {
            e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
            return client.ScoreAsync(ip, ctx.RequestAborted);
        });
        if (score >= 75)
        {
            ctx.Response.StatusCode = StatusCodes.Status403Forbidden;
            return;
        }
    }
    await next();
});

Behind a reverse proxy, call app.UseForwardedHeaders() with your proxy's addresses as KnownProxies first, so RemoteIpAddress is the visitor.

Need a hand?

If something here does not work with your version of C# and .NET, or you have improved it, tell us. We update these pages with what people send.

C# and .NET and its logo are trademarks of their respective owners. isipsafe is not affiliated with or endorsed by them.