Bastion is a production-ready authentication and security monitoring API built with ASP.NET Core 10. It goes beyond traditional login systems by actively detecting and logging security threats in real time โ including honeytoken access, impossible travel anomalies, decoy endpoint probing, and SQL injection attempts.
Traditional auth systems silently succeed or fail. Bastion gives security teams visibility into who is attacking, how they're attacking, and where they're coming from โ all surfaced through a live dashboard with geographical mapping and health monitoring.
| Icon | Feature | Details |
|---|---|---|
| ๐ | BCrypt + JWT Authentication | Password hashing with BCrypt, stateless JWT tokens (HS256, 24h expiry) |
| ๐ก๏ธ | SQL Injection Detection | 7 regex patterns blocking tautologies, UNION, DDL, comments, stacked queries |
| ๐จ | Canary/Honeytoken Account | Fake administrator account; any login attempt triggers CANARY_TRIGGERED |
| Impossible Travel Detection | Haversine distance calculation; flags logins >900 km/h between successive locations | |
| ๐ฏ | Decoy Endpoints | /api/admin/export-users, /.env, /api/backup/download โ 404 + DECOY_TRIGGERED |
| ๐บ๏ธ | Live Attack Map | Leaflet.js map with color-coded markers (red = threat, blue = normal) |
| ๐ | Health Gauge | Circular gauge โ green (safe), yellow pulse (1-2 threats), red pulse (3+) |
| ๐ฎ | Demo Mode | One-click attack simulation: 6 failed logins + canary touch + 2 impossible-travel logins |
| ๐ | Rate Limiting & Lockout | 10 req/15min sliding window per IP; 5 failed attempts = 30min account lockout |
| ๐ | GeoIP Resolution | MaxMind GeoIP2 integration for Country/City location on every login |
Bastion follows Clean Architecture principles with the Repository Pattern:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Controllers (API) โ
โ AuthController ยท DemoController โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Services (Business Logic) โ
โ AuthService ยท SecurityPatterns โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Interfaces (Abstractions) โ
โ IRepository<T> ยท IAuthService โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Data (Persistence) โ
โ SqlRepository<T> ยท AppDbContext ยท JsonRepository โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Models (Domain) โ
โ User ยท LoginAttempt โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Database-agnostic โ Swap SQL Server for PostgreSQL, MySQL, or JSON files by changing only the Data layer
- Testable โ Inject mock repositories into services for unit testing
- Maintainable โ Clear separation of concerns; each layer has a single responsibility
- Security-first โ Validation at controller and service layers (defense in depth)
| Layer | Technology |
|---|---|
| Backend | C# 12 / .NET 10 |
| Framework | ASP.NET Core Web API |
| Database | SQL Server + Entity Framework Core 10 |
| Authentication | BCrypt.Net-Next + JWT (System.IdentityModel.Tokens.Jwt) |
| GeoIP | MaxMind.GeoIP2 6.0 |
| Logging | Serilog (console + rolling file) |
| Frontend | HTML5 / CSS3 / Vanilla JS |
| Map | Leaflet.js 1.9 + OpenStreetMap tiles |
| Database Migrations | EF Core Migrations |
- .NET 10 SDK
- SQL Server (LocalDB, Express, or full instance)
- Git
# Clone the repository
git clone https://github.com/AbdullahAlHadabi/Bastion.git
cd Bastion
# Restore dependencies
dotnet restore
# Create the database and apply migrations
dotnet ef database update
# Initialize user secrets (required for JWT key and DemoMode)
dotnet user-secrets init
dotnet user-secrets set "Jwt:Key" "YourSuperSecretKeyHere123!@#"
dotnet user-secrets set "DemoMode" "true"
# Run the application
dotnet run --urls http://localhost:5000
# (Optional) Download GeoIP database for real IP lookups:
# Place GeoLite2-City.mmdb in the Data/ folder
# https://dev.maxmind.com/geoip/geolite2-free-geolocation-dataNote: DemoMode must be
"true"(string, not boolean) and the environment must beDevelopmentfor user-secrets to load.$env:ASPNETCORE_ENVIRONMENT = "Development"
# Health check
curl http://localhost:5000/
# Register a user
curl -X POST http://localhost:5000/api/auth/register \
-H "Content-Type: application/json" \
-d "{\"username\":\"demo\",\"password\":\"Demo@123\"}"
# Login
curl -X POST http://localhost:5000/api/auth/login \
-H "Content-Type: application/json" \
-d "{\"username\":\"demo\",\"password\":\"Demo@123\"}"
# Open the dashboard
start http://localhost:5000/dashboard.html
# Simulate an attack
curl -X POST http://localhost:5000/api/demo/simulate-attackRegister a new user account.
// Request
{ "username": "johndoe", "password": "P@ssw0rd!" }
// Response 201 Created
{ "message": "Registration successful." }
// Response 409 Conflict
{ "message": "Username already exists." }
// Response 400 Bad Request (SQL injection detected)
{ "message": "Invalid input detected." }Authenticate and receive a JWT token.
// Request
{ "username": "johndoe", "password": "P@ssw0rd!" }
// Response 200 OK
{ "token": "eyJhbGciOiJIUzI1NiIs...", "message": "Login successful." }
// Response 401 Unauthorized
{ "message": "Invalid credentials." }
// Response 429 Too Many Requests (rate limited)
{ "message": "Rate limit exceeded. Try again later." }Returns the last 100 login attempts (ordered newest first).
// Response 200 OK
[
{
"id": 42,
"username": "johndoe",
"success": true,
"ipAddress": "51.5074.0.1",
"eventType": "NORMAL",
"country": "United Kingdom",
"city": "London",
"latitude": 51.5074,
"longitude": -0.1278,
"timestamp": "2026-07-10T20:00:00Z"
}
]Returns distinct usernames with successful logins in the last 24 hours.
// Response 200 OK
["johndoe", "traveluser", "verify_user"]Runs a background attack simulation (requires DemoMode=true). Performs:
- 6 failed login attempts (random IPs)
- 1 canary account touch (
CANARY_TRIGGERED) - 2 distant successful logins (
IMPOSSIBLE_TRAVEL)
// Response 200 OK
{ "message": "Attack simulation started. Check logs for details." }
// Response 403 Forbidden (DemoMode disabled)
{ "message": "Demo mode is disabled." }These endpoints return 404 Not Found and log DECOY_TRIGGERED:
| Endpoint | Purpose |
|---|---|
GET /api/admin/export-users |
Mimics data exfiltration |
GET /.env |
Mimics environment file scraping |
GET /api/backup/download |
Mimics backup download |
| Event Type | Severity | Description |
|---|---|---|
NORMAL |
โ Info | Standard successful or failed login |
CANARY_TRIGGERED |
๐ด Critical | Honeytoken administrator account was accessed |
IMPOSSIBLE_TRAVEL |
๐ด Critical | User logged in from two distant locations faster than physically possible (>900 km/h) |
DECOY_TRIGGERED |
๐ก Warning | Decoy endpoint was probed by an attacker |
| Column | Type | Constraints |
|---|---|---|
Id |
int |
PK, Identity |
Username |
nvarchar(max) |
NOT NULL |
PasswordHash |
nvarchar(max) |
NOT NULL |
Role |
nvarchar(max) |
NOT NULL ("User" or "Admin") |
FailedAttempts |
int |
NOT NULL |
LockedUntil |
datetime2 |
NULL |
LastLoginLat |
float |
NULL |
LastLoginLon |
float |
NULL |
LastLoginAt |
datetime2 |
NULL |
CreatedAt |
datetime2 |
NOT NULL |
| Column | Type | Constraints |
|---|---|---|
Id |
int |
PK, Identity |
Username |
nvarchar(max) |
NOT NULL |
Success |
bit |
NOT NULL |
IpAddress |
nvarchar(max) |
NOT NULL |
EventType |
nvarchar(max) |
NOT NULL |
FailureReason |
nvarchar(max) |
NULL |
Country |
nvarchar(max) |
NULL |
City |
nvarchar(max) |
NULL |
Latitude |
float |
NULL |
Longitude |
float |
NULL |
Timestamp |
datetime2 |
NOT NULL |
The live security dashboard is served at /dashboard.html and features:
| Component | Description |
|---|---|
| ๐บ๏ธ Live Attack Map | Leaflet.js map with red markers for threats, blue for normal; auto-fits bounds |
| ๐ Health Gauge | Circular gauge with breathing animation: ๐ข Green (0 threats), ๐ก Yellow pulse (1-2), ๐ด Red pulse (3+) |
| ๐จ Threat Alerts | Scrollable list of non-normal events with color-coded left border (red = threat, blue = normal) |
| ๐ Recent Attempts | Table showing last 50 attempts with time, username, IP, location, event type, and status |
| ๐ Auto-Refresh | Polls every 3 seconds for new data |
| ๐ฎ Simulate Attack | One-click button triggers the full attack simulation |
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ BASTION SECURITY DASHBOARD [Simulate Attack] โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ Threats โ โ Attempts โ โ Sessions โ โ
โ โ 3 โ โ 39 โ โ 4 โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Live Attack Map โ โ Health Gauge โ โ
โ โ [Leaflet Map] โ โ โ [3] โ โ
โ โ โ Red (New York) โ โ (Red pulsing) โ โ
โ โ โ Blue (London) โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Threat Alerts โ โ
โ โ ๐ด IMPOSSIBLE_TRAVEL โ traveluser @ NY (20:09) โ โ
โ โ ๐ด CANARY_TRIGGERED โ administrator @ 10.0.0.1 โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Recent Attempts โ โ
โ โ Time โ User โ IP โ Location โ Event โ โ
โ โ 20:09 โ travel โ 40.71.. โ NY, USA โ IMPOS.. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
| Test | Endpoint | Expected | Status |
|---|---|---|---|
| Root health check | GET / |
200 OK |
โ |
| Register new user | POST /api/auth/register |
201 Created |
โ |
| Register duplicate | POST /api/auth/register |
409 Conflict |
โ |
| SQL injection (tautology) | POST /api/auth/register |
400 Bad Request |
โ |
| SQL injection (DROP TABLE) | POST /api/auth/register |
400 Bad Request |
โ |
| Valid login | POST /api/auth/login |
200 OK + JWT |
โ |
| Wrong password | POST /api/auth/login |
401 Unauthorized |
โ |
| Wrong username | POST /api/auth/login |
401 Unauthorized |
โ |
| Rate limiting | POST /api/auth/login |
429 Too Many Requests |
โ |
| Account lockout (5 fails) | POST /api/auth/login |
401 + locked |
โ |
| Get attempts | GET /api/auth/attempts |
200 OK |
โ |
| Active sessions | GET /api/auth/active-sessions |
200 OK |
โ |
| Canary trigger | Login as administrator |
CANARY_TRIGGERED |
โ |
| Impossible travel | London โ New York | IMPOSSIBLE_TRAVEL |
โ |
| Attack simulation | POST /api/demo/simulate-attack |
200 OK |
โ |
| Decoy endpoints | GET /api/admin/export-users |
404 + DECOY_TRIGGERED |
โ |
| Dashboard | GET /dashboard.html |
200 OK |
โ |
# Manual API tests with curl
curl http://localhost:5000/
curl -X POST http://localhost:5000/api/auth/register -H "Content-Type: application/json" -d "{\"username\":\"test\",\"password\":\"Test@123\"}"
curl -X POST http://localhost:5000/api/auth/login -H "Content-Type: application/json" -d "{\"username\":\"test\",\"password\":\"Test@123\"}"
# Check database directly (PowerShell + SqlServer module or SSMS)
Invoke-Sqlcmd -Query "SELECT EventType, COUNT(*) as Count FROM LoginAttempts GROUP BY EventType" -ServerInstance "localhost" -Database "BastionDb"Bastion/
โโโ Controllers/
โ โโโ AuthController.cs # Register, Login, attempts, sessions
โ โโโ DemoController.cs # Attack simulation, decoy endpoints
โโโ Services/
โ โโโ AuthService.cs # Business logic: auth, GeoIP, impossible travel
โโโ Interfaces/
โ โโโ IRepository.cs # Generic repository contract
โโโ Data/
โ โโโ AppDbContext.cs # EF Core DbContext
โ โโโ SqlRepository.cs # SQL Server implementation
โ โโโ JsonFileContext.cs # JSON file implementation (legacy)
โ โโโ JsonRepository.cs # JSON file repository
โ โโโ DesignTimeDbContextFactory.cs
โโโ Models/
โ โโโ User.cs # User entity
โ โโโ LoginAttempt.cs # Login attempt entity
โโโ Security/
โ โโโ SecurityPatterns.cs # SQL injection regex detection
โโโ Migrations/ # EF Core migrations
โโโ wwwroot/
โ โโโ dashboard.html # Live security dashboard
โโโ Program.cs # App startup, DI, middleware
โโโ appsettings.json # Configuration (connection string, etc.)
โโโ appsettings.Development.json # Development overrides
โโโ Bastion.csproj # Project file
โโโ Properties/
โ โโโ launchSettings.json # Launch profiles
โโโ Data/
โโโ GeoLite2-City.mmdb # MaxMind GeoIP database (download separately)
- Window: 10 requests per 15-minute sliding window
- Scope: Per IP address
- Implementation: In-memory
ConcurrentDictionary<string, List<DateTime>>with lock-free reads
- Threshold: 5 failed attempts
- Duration: 30 minutes
- Reset: Successful login resets the counter
Input is validated at both the controller layer (AuthController.ContainsSqlInjection) and the service layer (SecurityPatterns.IsMalicious) for defense in depth.
Seven regex patterns are checked against both username and password fields on every register and login request:
// SQL Injection Detection โ 7 Regex Patterns
private bool ContainsSqlInjection(string input)
{
var patterns = new[]
{
@"(\b(OR|AND)\b\s*['""]?\d+['""]?\s*=\s*['""]?\d+['""]?)", // Tautology
@"UNION\s+SELECT", // Union
@"\b(DROP|ALTER|EXEC|EXECUTE)\b", // DDL
@"--", // Inline comment
@"\/\*.*\*\/", // Block comment
@";\s*(DROP|SELECT|INSERT)", // Stacked queries
@"\bOR\b\s+.*\bOR\b" // Chained OR
};
return patterns.Any(p => Regex.IsMatch(input, p, RegexOptions.IgnoreCase));
}| # | Pattern | Targets | Example Blocked |
|---|---|---|---|
| 1 | Tautology | OR/AND with numeric comparison |
' OR '1'='1 |
| 2 | Union | UNION SELECT |
' UNION SELECT * FROM Users |
| 3 | DDL | DROP, ALTER, EXEC, EXECUTE |
' DROP TABLE Users -- |
| 4 | Inline comment | -- |
admin'-- |
| 5 | Block comment | /* */ |
'/**/OR/**/1=1-- |
| 6 | Stacked queries | ; followed by SQL |
'; DROP TABLE Users -- |
| 7 | Chained OR | Two or more OR clauses |
' OR 'a'='a' OR 'b'='b |
When a match is found, the API returns 400 Bad Request with {"message":"Invalid input detected."} โ the request is rejected before reaching any database operation.
- Pre-seeded
administratoraccount with a random 128-char password - Any login attempt โ
CANARY_TRIGGEREDevent - Password is intentionally unknown (generated with
Guid.NewGuid())
- Algorithm: Haversine formula for great-circle distance
- Threshold: >900 km/h (roughly commercial aircraft speed)
- Resolution: Demo IPs (
51.5074.0.1= London,40.7128.0.1= New York) or real MaxMind GeoIP2 - Verification: Travel from London to New York in <5 seconds โ detected
Using IRepository<T> meant we could swap JSON file storage for SQL Server without changing a single line of service code. The same interface supports both JsonRepository and SqlRepository โ proving that Clean Architecture pays off in real projects.
Security isn't a feature โ it's an architectural concern. Validating input at the controller and service layer (defense in depth), rate-limiting before auth checks, and logging every attempt (not just failures) are patterns every auth system should follow.
The 3-second polling dashboard turned abstract events (log entries) into an engaging visual experience. Security teams can spot attack patterns at a glance โ the map makes impossible travel immediately obvious.
The DemoMode flag (stored in user-secrets) keeps demo functionality isolated. Attack simulations run in background tasks with their own DI scopes, preventing side effects like disposed DbContext errors.
This project is licensed under the MIT License โ see the LICENSE file for details.
MIT License
Copyright (c) 2026 Abdullah Al-Hadabi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
- Abdullah Al-Hadabi โ Full-stack security engineer and project architect
- MaxMind for the GeoLite2 free geolocation database
- Leaflet.js for the open-source mapping library
- OpenStreetMap for tile services
- BCrypt.Net for reliable password hashing
- Serilog for structured logging
- The ASP.NET Core team for the robust web framework
Built with ๐ด by Abdullah Al-Hadabi
Because security shouldn't be invisible.