Apped.BffAuth 2025.1.4

dotnet add package Apped.BffAuth --version 2025.1.4
                    
NuGet\Install-Package Apped.BffAuth -Version 2025.1.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Apped.BffAuth" Version="2025.1.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Apped.BffAuth" Version="2025.1.4" />
                    
Directory.Packages.props
<PackageReference Include="Apped.BffAuth" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Apped.BffAuth --version 2025.1.4
                    
#r "nuget: Apped.BffAuth, 2025.1.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Apped.BffAuth@2025.1.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Apped.BffAuth&version=2025.1.4
                    
Install as a Cake Addin
#tool nuget:?package=Apped.BffAuth&version=2025.1.4
                    
Install as a Cake Tool

Apped.BffAuth

Secure Backend-for-Frontend (BFF) authentication for .NET 8 APIs with OAuth/OIDC support, multi-tenancy, and complete Swagger UI integration.

🚀 Quick Start Guide

1. Installation

dotnet add package Apped.BffAuth --version 2025.1.4

2. 🔄 Migration Guide (Adding to Existing Project)

📋 What You Need to Do:

Step 1: Keep Swagger services AND add BFF Auth

// ✅ KEEP your existing Swagger services (required for core functionality):
builder.Services.AddSwaggerGen();

// ✅ ADD BFF Auth with Swagger integration:
builder.Services.AddBffAuth("your-client-id", "your-client-secret", 
    "https://auth.example.com/realms/your-realm", "your-32-char-signing-key")
    .EnableSwagger("Your API Name", "Description");

Step 3: Update your middleware

// ✅ KEEP this unchanged:
if (app.Environment.IsDevelopment()) {
    app.UseSwagger();
    
    // ❌ REMOVE this line:
    // app.UseSwaggerUI();
    
    // ✅ REPLACE with BFF-integrated Swagger UI:
    app.UseBffAuthSwagger(); // Swagger UI with OAuth integration
}

// ✅ ADD this for authentication:
app.UseBffAuth(); // Handles BFF authentication middleware

🎯 That's it! Your Swagger UI now has OAuth authentication with popup login.

🤖 For AI Agents & Future Developers

CRITICAL: Correct Configuration Pattern

The BFF Auth Swagger integration requires BOTH standard Swagger services AND BFF Auth integration:

// ✅ REQUIRED: Core Swagger services (DO NOT REMOVE)
builder.Services.AddSwaggerGen();

// ✅ REQUIRED: BFF Auth integration (adds HTML injection)
builder.Services.AddBffAuth(config)
    .EnableSwagger("Title", "Description");

// ✅ REQUIRED: Use BFF Auth Swagger UI (NOT standard UseSwaggerUI)
app.UseBffAuthSwagger();

Common Mistakes That Break HTML Injection

// ❌ WRONG: Missing AddSwaggerGen() causes "ISwaggerProvider not found" error
builder.Services.AddBffAuth(config)
    .EnableSwagger("Title", "Description");
// Missing: builder.Services.AddSwaggerGen();

// ❌ WRONG: Using UseSwaggerUI() prevents BFF Auth HTML injection
app.UseSwaggerUI(); // This blocks the custom authentication widget

// ❌ WRONG: Forgetting .EnableSwagger() means no HTML injection happens
builder.Services.AddBffAuth(config);
// Missing: .EnableSwagger("Title", "Description");

🎯 HTML Injection Verification

When correctly configured, you should see:

  • Custom "BFF Authentication" widget at top of Swagger UI
  • Sign In/Sign Out buttons with tenant ID input
  • Real-time authentication status display
  • No standard Swagger "Authorize" button (replaced by BFF widget)

🏢 Multi-Tenancy: Header-Only Approach (v2025.1.4+)

BREAKING CHANGE: Tenant ID is now passed via headers only, NOT in request body.

// ✅ CORRECT: Tenant passed via header
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Tenant-ID", "tenant123");
await client.PostAsync("/auth/start", new StringContent("{\"redirect_hint\": \"...\"}"));

// ❌ WRONG: Tenant in request body (deprecated since v2025.1.4)
await client.PostAsync("/auth/start", new StringContent("{\"redirect_hint\": \"...\", \"tenant_id\": \"tenant123\"}"));

The Swagger UI widget automatically handles this - enter tenant ID in the widget and it's sent via X-Tenant-ID header.

3. Alternative Setup Methods

If you prefer configuration files:

// appsettings.json
{
  "BffAuth": {
    "Providers": {
      "Keycloak": {
        "ClientId": "your-client-id",
        "ClientSecret": "your-client-secret",
        "RealmBaseUrl": "https://auth.example.com/realms/your-realm"
      }
    },
    "Security": {
      "SigningKey": "your-32-character-signing-key-here"
    }
  }
}

// Program.cs
builder.Services.AddBffAuth(builder.Configuration.GetSection("BffAuth"))
    .EnableSwagger("My API", "Description");

If you need advanced settings:

builder.Services.AddBffAuth(options => {
    options.ConfigureKeycloak()
        .WithClientCredentials("client-id", "secret")
        .WithRealmUrl("https://auth.example.com/realms/test")
        .WithScopes("openid", "profile", "email", "roles")
        .And()
    .ConfigureSecurity()
        .WithSigningKey("your-32-char-signing-key")
        .And()
    .ConfigureSessions()
        .WithSessionTtlMinutes(120) // 2 hours
        .And()
    .EnableSwagger("My API", "Description");
});

3. Configuration (Option A Only)

Add to your appsettings.json:

{
  "BffAuth": {
    "Providers": {
      "keycloak": {
        "Type": "keycloak",
        "ClientId": "your-client-id",
        "ClientSecret": "your-client-secret",
        "AuthorizationEndpoint": "https://your-keycloak/realms/your-realm/protocol/openid-connect/auth",
        "TokenEndpoint": "https://your-keycloak/realms/your-realm/protocol/openid-connect/token",
        "UserInfoEndpoint": "https://your-keycloak/realms/your-realm/protocol/openid-connect/userinfo",
        "Issuer": "https://your-keycloak/realms/your-realm",
        "Scopes": ["openid", "profile", "email"],
        "UsePkce": true,
        "RedirectUris": [
          "myapp://callback",
          "https://myapp.com/callback"
        ]
      }
    },
    "Security": {
      "SigningKey": "your-super-secret-signing-key-at-least-32-characters"
    }
  }
}

✨ What's New in v2025.1.4

🔧 Latest Updates

  • BREAKING CHANGE: Tenant ID no longer accepted in request body for /auth/start
  • Header-based tenancy: All tenant information now passed via X-Tenant-ID header only
  • Consistent tenant handling: Unified approach across all authentication endpoints
  • Updated examples: All documentation reflects current header-based approach

✨ What's New in v2025.1.1

🎯 Self-Guiding Fluent Builder API

  • IntelliSense-driven setup: Discover all configuration options through autocomplete
  • 3 setup approaches: Choose what works for your project (config, fluent, or quick)
  • Built-in validation: Helpful error messages guide you to correct configuration
  • One-line middleware: app.UseBffAuth() replaces all previous middleware calls
  • Backward compatible: Existing code continues to work (marked as obsolete)

🔧 Enhanced Developer Experience

  • Self-documenting builders: Method names clearly indicate what they configure
  • Preset configurations:
    • WithDevelopmentDefaults() - Optimized for local development and testing
    • WithProductionDefaults() - Security-first settings for production
    • WithSecureDefaults() - Maximum security configuration
  • Automatic endpoint generation: All auth endpoints work out-of-the-box

🌐 Complete Swagger UI Integration

  • "Authorize (BFF)" button with professional UI design
  • Popup-based OAuth flow - no page redirects
  • Session persistence across browser refreshes
  • Real-time authentication status with user details and session expiry
  • Multi-tenant support with tenant ID input field
  • Automatic header injection for all API calls:
    • Authorization: Session {sessionId}
    • X-Tenant-ID: {tenantId} (when provided)

🚀 Streamlined Setup Process

Before:

// 4+ method calls, complex configuration
builder.Services.AddBffAuthentication(builder.Configuration);
builder.Services.AddBffAuthSwagger();
app.UseBffAuthSwaggerAssets();
app.UseBffAuthentication();

After:

// 2 method calls, self-guiding configuration
builder.Services.AddBffAuth(config => config.WithDevelopmentDefaults());
app.UseBffAuth();

Automatic Endpoints

The package automatically registers these endpoints when you call app.UseBffAuth():

🔐 Authentication Endpoints

  • POST /auth/start - Start OAuth flow, returns authorization URL and session ID
  • POST /auth/callback - Handle OAuth callback, returns authenticated session
  • POST /auth/refresh - Refresh expired session with new tokens
  • POST /auth/logout - Logout user and invalidate session
  • POST /auth/logout-url - Get provider logout URL for complete logout

👤 Session Info Endpoint

  • GET /auth/me - Get current session information including user details, roles, and expiry timestamps (requires session)

🔧 Swagger Integration Endpoints (when using AddBffAuthSwagger)

  • GET /swagger-auth/callback - OAuth callback handler for Swagger UI popup integration
  • GET /bff-auth-swagger.js - Custom JavaScript for Swagger UI integration
  • GET /bff-auth-swagger.css - Custom styles for Swagger UI integration

📚 API Reference

Request/Response Models

AuthStartRequest
{
  "redirect_hint": "string" // Required: OAuth redirect URI hint for provider selection
}
AuthStartResponse
{
  "sid": "string",           // Session ID for callback
  "authorize_url": "string", // OAuth provider authorization URL
  "expires_in": 600          // Expiration time in seconds (default: 10 minutes)
}
AuthCallbackRequest
{
  "sid": "string",           // Required: Session ID from start response
  "code": "string",          // Required: OAuth authorization code
  "state": "string",         // Required: OAuth state parameter
  "user_agent": "string",    // Optional: User agent for device binding
  "device_id": "string"      // Optional: Device identifier for device binding
}
AuthCallbackResponse
{
  "session_id": "string",    // New authenticated session ID
  "user": {                  // User information and roles
    "sub": "string",         // User ID
    "email": "string",       // Email address
    "name": "string",        // Display name
    "username": "string",    // Username
    "firstName": "string",   // First name
    "lastName": "string",    // Last name
    "globalRoles": ["string"], // Global/realm roles
    "tenants": [             // Tenant-specific roles
      {
        "tenantId": "string",
        "roles": ["string"]
      }
    ]
  },
  "expires_in": 3600         // Session expiration in seconds
}
SessionInfo Response (GET /auth/me)
{
  "sessionId": "string",     // Current session identifier
  "user": {                  // Complete user information (same as above)
    "sub": "string",
    "email": "string", 
    "name": "string",
    "username": "string",
    "firstName": "string",
    "lastName": "string",
    "globalRoles": ["string"],
    "tenants": [
      {
        "tenantId": "string",
        "roles": ["string"] 
      }
    ]
  },
  "expiresAt": "2024-01-15T10:30:00Z",    // Session expiration timestamp (ISO 8601)
  "createdAt": "2024-01-15T09:00:00Z",    // Session creation timestamp
  "lastAccessedAt": "2024-01-15T10:25:00Z", // Last access timestamp  
  "expiresIn": 300,          // Seconds until expiration
  "isExpired": false         // Whether session has expired
}
UserInfo Model (returned by /auth/me)
{
  "sub": "string",           // Unique user identifier
  "email": "string",         // User email
  "name": "string",          // Full display name
  "username": "string",      // Username/login
  "firstName": "string",     // First name
  "lastName": "string",      // Last name
  "globalRoles": ["string"], // Global roles (e.g., ["admin", "user"])
  "tenants": [               // Per-tenant role assignments
    {
      "tenantId": "tenant1",
      "roles": ["manager", "user"]
    }
  ]
}

🌐 Swagger UI Integration Guide

The package provides seamless integration with Swagger UI, adding a professional OAuth authentication experience directly in your API documentation.

Complete Integration Setup (New API)

// Program.cs
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c => {
    c.SwaggerDoc("v1", new OpenApiInfo 
    { 
        Title = "My API", 
        Version = "v1" 
    });
    // No manual security scheme configuration needed!
});

// Single call enables everything (authentication + swagger integration)
builder.Services.AddBffAuth(builder.Configuration.GetSection("BffAuth"))
    .EnableSwagger(); // This handles all Swagger integration automatically

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(); // Standard Swagger UI, no changes needed
}

// Single middleware call handles everything
app.UseBffAuth(); // Replaces UseBffAuthSwaggerAssets + UseBffAuthentication

app.UseRouting();
app.MapControllers();
app.Run();

🔧 Advanced Configuration

If you need custom Swagger title/description:

builder.Services.AddBffAuth(options => {
    options.ConfigureKeycloak()
        .WithClientCredentials("client-id", "client-secret")
        .WithRealmUrl("https://auth.example.com/realms/myrealm")
        .And()
    .EnableSwagger("Custom API Title", "Custom description");
});

If you store config in appsettings.json:

// appsettings.json
{
  "BffAuth": {
    "Providers": {
      "Keycloak": {
        "ClientId": "your-client-id",
        "ClientSecret": "your-client-secret", 
        "RealmBaseUrl": "https://auth.example.com/realms/your-realm"
      }
    },
    "Security": {
      "SigningKey": "your-32-character-signing-key-here"
    }
  }
}

// Program.cs  
builder.Services.AddBffAuth(builder.Configuration.GetSection("BffAuth"))
    .EnableSwagger("My API", "Description");

🔍 Troubleshooting

Problem: "Swagger is already configured" error

  • Fix: This error is outdated - you now NEED both AddSwaggerGen() AND .EnableSwagger()
  • Current approach: Keep AddSwaggerGen() for core Swagger, add .EnableSwagger() for BFF Auth integration

Problem: "Not Found /swagger/v1/swagger.json"

  • Fix: Make sure you're using app.UseBffAuthSwagger() instead of app.UseSwaggerUI()
  • Fix: Keep app.UseSwagger() but replace UseSwaggerUI() with UseBffAuthSwagger()

Problem: No "Authorize (BFF)" button in Swagger

  • Fix: Make sure you called .EnableSwagger("Title", "Description")
  • Fix: Use app.UseBffAuthSwagger() instead of app.UseSwaggerUI()
  • Fix: Check browser console for JavaScript errors

Problem: Popup gets blocked during authentication

  • Fix: Allow popups for localhost in your browser settings

Problem: API calls don't include authentication

  • Fix: Click the "Authorize (BFF)" button and complete login first
  • Check: Look for bff-session-id in browser localStorage

Swagger Features

  • 🎯 Custom "Authorize (BFF)" Button: Replaces default Swagger authorization with integrated OAuth flow
  • 🔄 Popup Authentication: No page redirects, all authentication happens in popup window
  • 💾 Session Persistence: Sessions survive browser refreshes via localStorage
  • 🔗 Automatic Headers: All API requests automatically include Authorization: Session {sessionId}
  • 📱 Real-time Status: Button shows authentication state and user info
  • 🖥️ Development Ready: Works with localhost redirects and provides testing tools
  • 🎨 Professional UI: Beautiful gradient styling with hover effects and smooth transitions
  • 🔄 Smart Retry: Robust initialization that handles dynamic content loading

Authentication Flow

1. Start Authentication

POST /auth/start

{
  "redirect_hint": "myapp://callback"
}

Response:

{
  "sid": "opaque-session-id",
  "authorize_url": "https://keycloak.../auth?client_id=...",
  "expires_in": 600
}

2. Handle OAuth Callback

POST /auth/callback

{
  "sid": "session-id-from-start",
  "code": "oauth-authorization-code",
  "state": "oauth-state-parameter"
}

Response:

{
  "session_id": "new-authenticated-session-id",
  "user": {
    "sub": "user-123",
    "email": "user@example.com",
    "name": "John Doe",
    "globalRoles": ["admin"],
    "tenants": [
      { "tenantId": "tenant1", "roles": ["manager"] }
    ]
  },
  "expires_in": 3600
}

3. Use Session

Include in all API requests:

Authorization: Session your-session-id

4. Protect Your Endpoints

[RequireSession]
public class MyController : ControllerBase
{
    [RequireRole("admin")]
    public IActionResult AdminOnly() => Ok();
    
    [RequireRole("manager", requireCurrentTenant: true)]
    public IActionResult TenantManager(string tenantId) => Ok();
    
    public IActionResult GetUserInfo()
    {
        var user = (UserInfo)HttpContext.Items["User"]!;
        return Ok(user);
    }
}

📖 Self-Guiding Builder API Reference

The fluent builder API is designed to be discoverable through IntelliSense. Simply start typing and let your IDE guide you through all available options.

Core Builder Methods

AddBffAuth() - Main Entry Point
// Method 1: Configuration-based (reads from appsettings.json)
services.AddBffAuth(configuration.GetSection("BffAuth"))

// Method 2: Fluent configuration with lambda
services.AddBffAuth(builder => { /* configure */ })

// Method 3: Quick setup with parameters
services.AddBffAuth("clientId", "clientSecret", "realmUrl", "signingKey", "redirect1", "redirect2")
ConfigureKeycloak() - OAuth Provider Setup
.ConfigureKeycloak()
    .WithClientCredentials("client-id", "client-secret")    // Required
    .WithRealmUrl("https://auth.example.com/realms/realm")  // Auto-generates endpoints
    .WithScopes("openid", "profile", "email")               // OAuth scopes
    .WithRedirectUris("myapp://callback", "https://...")    // Callback URLs
    .And() // Return to main builder
ConfigureSecurity() - Security Settings
.ConfigureSecurity()
    .WithSigningKey("32-char-minimum-signing-key")  // JWT signing key
    .WithGeneratedSigningKey()                      // Auto-generate for development
    .RequireHttps(true)                            // HTTPS enforcement
    .ValidateIssuer(true)                          // Token validation
    .ValidateAudience(true)                        // Token validation
    .ValidateAll()                                 // Enable all validations
    .WithProductionDefaults("signing-key")         // Secure preset
    .WithDevelopmentDefaults("signing-key")        // Development preset
    .And()
ConfigureSessions() - Session Management
.ConfigureSessions()
    .WithSessionTtl(TimeSpan.FromMinutes(60))      // Session lifetime
    .WithSessionTtlMinutes(60)                     // Session lifetime (minutes)
    .WithPendingAuthTtl(TimeSpan.FromMinutes(10))  // Auth flow timeout
    .WithPendingAuthTtlMinutes(10)                 // Auth flow timeout (minutes)
    .EnableDeviceBinding()                         // Device security
    .EnableUserAgentBinding()                      // Browser security
    .UseRedis("connection-string")                 // Redis storage
    .UseInMemory()                                 // In-memory storage
    .WithSecureDefaults()                          // Production preset
    .WithDevelopmentDefaults()                     // Development preset
    .And()
ConfigureSwagger() - Swagger UI Integration
.ConfigureSwagger()                               // Enable Swagger integration
    .WithTitle("My API")                          // Swagger document title
    .WithDescription("API Description")           // Document description
    .WithCallbackRoute("/auth/swagger-callback")  // OAuth callback route
    .EnableAuthButton(true)                       // Show auth button
    .WithDefaults("Custom Title")                 // Apply recommended settings
    .And()

// OR use the convenience method:
.EnableSwagger("My API", "API Description")       // Quick setup with title/description

Preset Configurations

Development Presets - Optimized for Local Development
// Security: Relaxed HTTPS, generated signing key
.ConfigureSecurity().WithDevelopmentDefaults()

// Sessions: Longer duration, no device binding
.ConfigureSessions().WithDevelopmentDefaults()
Production Presets - Maximum Security
// Security: HTTPS required, all validations enabled
.ConfigureSecurity().WithProductionDefaults("your-signing-key")

// Sessions: Shorter duration, full device binding
.ConfigureSessions().WithSecureDefaults()

Complete Examples

Development Setup
builder.Services.AddBffAuth(options => {
    options.ConfigureKeycloak()
        .WithClientCredentials("dev-client", "dev-secret")
        .WithRealmUrl("http://localhost:8080/realms/dev")
        .WithRedirectUris("http://localhost:5058/callback")
        .And()
    .ConfigureSecurity()
        .WithDevelopmentDefaults() // Generated key, no HTTPS
        .And()
    .ConfigureSessions()
        .WithDevelopmentDefaults() // 60min sessions, no binding
        .And()
    .ConfigureSwagger()
        .WithDefaults("Development API")
        .And();
});
Production Setup
builder.Services.AddBffAuth(options => {
    options.ConfigureKeycloak()
        .WithClientCredentials("prod-client", Environment.GetEnvironmentVariable("CLIENT_SECRET"))
        .WithRealmUrl("https://auth.mycompany.com/realms/production")
        .WithRedirectUris("https://myapp.com/callback", "myapp://callback")
        .And()
    .ConfigureSecurity()
        .WithProductionDefaults(Environment.GetEnvironmentVariable("SIGNING_KEY"))
        .And()
    .ConfigureSessions()
        .WithSecureDefaults() // 30min sessions, full binding
        .And()
    .ConfigureSwagger()
        .WithDefaults("Production API")
        .And();
});

Features

  • Self-Guiding: IntelliSense reveals all configuration options
  • Secure: OAuth tokens never exposed to clients
  • PKCE: Enhanced security for all flows
  • Multi-tenant: Role-based access with tenant support
  • Redis: Automatic detection with in-memory fallback
  • Keycloak: Built-in provider support
  • Flexible: 3 setup approaches to fit any project

Multi-Tenancy

Keycloak groups from JWT tokens automatically map to tenant roles:

Example Token:

"groups": [
  "/tenants/tenant1",
  "/tenants/tenant2/manager"
]

Results in:

  • tenant1/user (default role)
  • tenant2/manager (specific role)

Access user info in controllers:

[RequireSession]
public IActionResult GetUser()
{
    var user = (UserInfo)HttpContext.Items["User"]!;
    return Ok(user);
}
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2025.1.4 157 8/25/2025
2025.1.3 152 8/25/2025
2025.1.2 145 8/25/2025
2025.1.1 267 8/25/2025
1.3.0 197 8/24/2025
1.0.5 55 8/23/2025
1.0.4 56 8/23/2025
1.0.3 59 8/23/2025
1.0.2 53 8/23/2025
1.0.1 53 8/23/2025
1.0.0 55 8/23/2025

v2025.1.4: Documentation and README Updates
     - IMPROVED: Updated repository and NuGet README documentation to reflect latest changes
     - IMPROVED: Accurate documentation of tenant_id header-based approach
     - IMPROVED: Current Swagger integration instructions
     - IMPROVED: Up-to-date examples and troubleshooting guidance
     
     v2025.1.3: Critical Tenant ID and Swagger Integration Fixes
     - BREAKING: Removed tenant_id from auth/start request body - now uses X-Tenant-ID header only
     - FIXED: Swagger UI integration documentation and middleware setup
     - FIXED: "Not Found /swagger/v1/swagger.json" error - now uses correct middleware replacement
     - FIXED: Sample application now properly demonstrates BFF Auth Swagger integration
     - IMPROVED: Consistent tenant handling across all endpoints (header-based only)
     - IMPROVED: Clear migration guide showing exactly what to remove/replace/keep
     - IMPROVED: Simplified documentation - eliminated confusing multiple approaches
     - IMPROVED: Comprehensive troubleshooting section for common Swagger issues
     - IMPROVED: Swagger conflict detection with helpful error messages
     - IMPROVED: Better code coverage reporting by excluding sample projects
     
     v2025.1.2: Documentation and Testing Improvements
     - IMPROVED: Enhanced test coverage for AuthController, BffAuthBuilder, SwaggerExtensions
     - IMPROVED: Visual code coverage integration and reporting
     - IMPROVED: Error handling and validation in fluent builders
     
     v2025.1.1: Self-Guiding Fluent Builder API
     - NEW: Self-guiding fluent builder API with IntelliSense-driven setup
     - NEW: 3 flexible setup approaches (config-based, fluent, quick setup)
     - NEW: Built-in validation with helpful error messages
     - NEW: Unified UseBffAuth() middleware (replaces 4+ method calls)
     - NEW: Preset configurations (WithDevelopmentDefaults, WithProductionDefaults)
     - NEW: Comprehensive README with self-documenting examples
     - IMPROVED: Complete Swagger UI integration with automatic setup
     - IMPROVED: Streamlined developer experience
     - BACKWARD COMPATIBLE: Existing code continues to work (marked obsolete)
     - BREAKING: None - fully backward compatible
     
     v1.2.0: Complete Swagger UI Integration
     - Full Swagger UI integration with "Authorize (BFF)" button
     - Popup-based OAuth authentication with session persistence
     - Automatic header injection and multi-tenant support
     - Real-time authentication status display
     
     v1.1.0: Enhanced Security and UX
     - Improved PKCE implementation and state parameter security
     - Better error handling and logging
     
     v1.0.0: Initial Release
     - Backend-for-Frontend authentication pattern
     - OAuth 2.0/OIDC with PKCE support
     - Keycloak integration and multi-tenancy
     - Redis and in-memory session storage