Het Backend For Frontend (BFF) pattern revolutioneert hoe we identity management aanpakken in moderne webapplicaties. Bij een relatie hebben we een productie-klare implementatie gebouwd die OpenIdDict combineert met Ocelot Gateway en Azure Redis Cache. In dit artikel deel ik onze complete architectuur met real-world code voorbeelden.
Inhoudsopgave
1. Wat is het BFF Pattern?
Het Backend For Frontend (BFF) pattern is een architectuurpatroon waarbij je een dedicated backend service creëert voor elke frontend client. In plaats van dat je frontend direct communiceert met microservices, gaat alle communicatie via de BFF layer die specifiek is afgestemd op de behoeften van dat frontend.
🎯 Waarom BFF?
- Security First: JWT tokens nooit exposed aan browser
- Multiple Clients: Web apps, mobile apps én administratie panels
- Complex Backend: 15+ microservices met verschillende protocols
- Compliance: Zorgvuldige omgang met gebruikersdata
- Performance: Intelligent caching en request aggregation
BFF Architectuur
React/Angular
BFF + API Gateway
Identity Provider
GoNative/Median
Token + Session Storage
Business Logic
2. Waarom BFF voor Identity Management?
Traditional SPA authentication met JWT tokens in localStorage brengt aanzienlijke security risico's met zich mee. Het BFF pattern lost deze problemen elegant op.
⚠️ Security Problemen van Traditional SPA Auth:
- XSS Vulnerability: JavaScript malware kan tokens uit localStorage stelen
- Token Exposure: Tokens zichtbaar in browser dev tools
- CSRF Complexity: Moeilijk te implementeren zonder cookies
- Token Refresh Hell: Complex refresh token management in SPA
- Session Management: Geen reliable manier om sessies server-side te beëindigen
✅ Hoe BFF deze problemen oplost:
- HttpOnly Cookies: Niet toegankelijk via JavaScript
- Same-Site Protection: Automatic CSRF protection
- Server-side Token Management: JWT tokens veilig in Redis cache
- Simplified Frontend: Frontend hoeft alleen cookies te versturen
- Centralized Security: Alle security logic geconcentreerd in gateway
- Session Control: Server-side session invalidation
3. Architectuur
Onze productie architectuur combineert verschillende proven technologies om een robuuste, schaalbare identity solution te creëren die zowel web als mobile clients ondersteunt.
Technology Stack
🚪 Ocelot Gateway
BFF + API Gateway funcionaliteit, request routing, rate limiting
🔐 OpenIdDict
OpenID Connect provider, token uitgifte, user authentication
⚡ Azure Redis
High-performance token caching, session storage, distributed cache
🏗️ ASP.NET Core
Gateway host, middleware pipeline, authentication providers
4. Gateway Startup Configuration
De gateway vormt het hart van onze BFF architectuur. Hier is onze complete production startup configuratie die alle componenten samenvoegt.
// Startup.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.DataProtection.Repositories;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.Net.Http.Headers;
using Microsoft.OpenApi.Models;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Serilog;
using System.Reflection;
using System.Security.Claims;
namespace Gateway;
public class Startup(IConfiguration configuration)
{
public IConfiguration Configuration { get; } = configuration;
public void ConfigureServices(IServiceCollection services)
{
// Monitoring en observability
services.AddMonitoring(typeof(Program).Assembly.FullName?.Split(",")[0]);
// Serilog configuratie
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
// Azure Redis Cache voor token storage
services.AddCaching(Configuration);
// Active company validation voor multi-tenancy
services.Configure<ActiveCompanyValidationOptions>(
Configuration.GetSection(ActiveCompanyValidationOptions.SectionName));
// Controllers voor BFF endpoints
services.AddControllersWithViews();
// Ocelot API Gateway met custom handlers
services.AddOcelot(Configuration)
.AddDelegatingHandler<DownstreamContentHeadersHandler>(true)
.AddDelegatingHandler<JwtHandler>(true) // JWT injection naar backend
.AddDelegatingHandler<CorsHandler>(true); // CORS handling
// Health checks voor monitoring
services.AddHealthChecks()
.AddCheck<IdentityHealthCheck>("IdentityHealthCheck");
// Forwarded headers voor load balancer support
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedHost |
ForwardedHeaders.XForwardedProto;
options.ForwardLimit = 2; // Limit proxy hops voor security
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
// Development tooling
if (Configuration.IsSwaggerEnabled())
{
services.AddSwaggerForOcelot(Configuration, (o) =>
{
o.GenerateDocsForGatewayItSelf = false;
});
}
// HTTPS redirection voor production
if (!Configuration.DisableHttps())
{
services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status308PermanentRedirect;
options.HttpsPort = 443;
});
}
// Identity model debugging (alleen development)
IdentityModelEventSource.ShowPII = true;
CSRF Protection & Data Protection Setup
// Anti-forgery token configuratie voor CSRF protection
services.AddAntiforgery(options =>
{
options.HeaderName = "X-XSRF-TOKEN";
options.Cookie.Name = "__Host-X-XSRF-TOKEN";
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
services.AddHttpClient();
services.AddOptions();
// Data Protection setup voor cookie encryption
services.SetupDataProtection("Gateway");
5. OpenIdConnect Integration
De authentication configuratie vormt de kern van onze BFF implementatie. We combineren cookie authentication met OpenIdConnect voor een seamless user experience.
// Authentication configuratie
var openIdConnectSettings = Configuration.GetSection("OpenIDConnectSettings");
var cookieDomain = Configuration["cookieDomain"];
// Development vs Production cookie domain handling
if (openIdConnectSettings["Authority"]?.Contains("localhost", StringComparison.InvariantCultureIgnoreCase) ?? false)
{
cookieDomain = "localhost";
}
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
// Cookie configuratie voor maximum security
options.Cookie.Name = AuthCookieName.CookieName;
options.Cookie.MaxAge = TokenSettings.CookieLifeSpan;
options.Cookie.HttpOnly = true; // XSS protection
options.Cookie.Domain = cookieDomain; // Cross-subdomain support
options.Cookie.IsEssential = true; // GDPR essentieel
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict; // CSRF protection
options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // HTTPS only
options.SlidingExpiration = false; // Fixed expiration
// Authentication flow paths
options.AccessDeniedPath = "/forbidden";
options.LoginPath = "/login";
options.LogoutPath = "/logout";
})
OpenIdConnect Provider Configuration
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
// Basic OIDC configuratie
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = openIdConnectSettings["Authority"];
options.ClientId = openIdConnectSettings["ClientId"];
options.ClientSecret = openIdConnectSettings["ClientSecret"];
options.RequireHttpsMetadata = false; // Development flexibiliteit
options.ResponseType = OpenIdConnectResponseType.Code; // Authorization Code flow
options.ResponseMode = "form_post"; // Security best practice
options.UsePkce = true; // PKCE voor extra security
// Callback paths
options.CallbackPath = "/signin-oidc";
options.SignedOutCallbackPath = "/signout-callback-oidc";
// Scopes configuratie
options.Scope.Clear();
options.Scope.Add("profile");
options.Scope.Add("roles");
options.Scope.Add("openid");
options.Scope.Add("offline_access"); // Voor refresh tokens
// Token storage - KRITIEK: SaveTokens = true voor token extractie
options.SaveTokens = true;
6. Token Caching Strategy
Het hart van onze BFF architectuur is de intelligente token caching strategy. We extraheeren JWT tokens uit de OpenIdConnect flow en slaan ze veilig op in Azure Redis.
🔐 Waarom Token Extraction?
- Security: JWT tokens nooit naar browser
- Performance: Redis veel sneller dan database lookups
- Scalability: Horizontaal schaalbaar via Redis clustering
- Mobile Support: Refresh tokens voor mobile apps
- Session Control: Server-side session invalidation mogelijk
// Event handlers voor token management
options.Events.OnRedirectToIdentityProvider = context =>
{
// Consumer app parameter doorgeven voor multi-app support
var consumerApp = context.Request.Query["consumer_app"].ToString();
if (!string.IsNullOrEmpty(consumerApp))
{
context.ProtocolMessage.RedirectUri += $"?consumer_app={consumerApp}";
}
return Task.CompletedTask;
};
options.Events.OnRedirectToIdentityProviderForSignOut = context =>
{
// Logout flow configuration
context.ProtocolMessage.ClientId = openIdConnectSettings["ClientId"];
var consumerApp = context.Request.Query["consumer_app"].ToString();
if (!string.IsNullOrEmpty(consumerApp))
{
context.ProtocolMessage.PostLogoutRedirectUri += $"?consumer_app={consumerApp}";
context.ProtocolMessage.RedirectUri += $"?consumer_app={consumerApp}";
}
return Task.CompletedTask;
};
// KRITIEKE EVENT: Token extraction en caching
options.Events.OnTicketReceived = async context =>
{
var cache = context.HttpContext.RequestServices.GetRequiredService<ICacheProvider>();
var identity = context!.Principal!.Identities.FirstOrDefault();
if (identity == null || !identity.IsAuthenticated)
{
context.Fail("Failed authentication");
return;
}
if (context.Properties != null)
{
// Extract user ID en tokens
var userId = identity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
var token = context.Properties.GetTokenValue("id_token");
var refreshToken = context.Properties.GetTokenValue("refresh_token");
if (token == null)
{
return;
}
// Clean previous token en store nieuwe
await cache.ClearAsync($"access_token_{userId}");
await cache.SetAsync($"access_token_{userId}", token, TokenSettings.TokenLifeSpan);
// SECURITY: Clear tokens uit cookie properties
// Anders zouden ze in de cookie terecht kunnen komen
context.Properties.UpdateTokenValue("access_token", string.Empty);
context.Properties.UpdateTokenValue("id_token", string.Empty);
context.Properties.UpdateTokenValue("expires_at", string.Empty);
context.Properties.UpdateTokenValue("token_type", string.Empty);
context.Properties.UpdateTokenValue("refresh_token", string.Empty);
Mobile App Refresh Token Handling
// Special handling voor mobile apps (GoNative/Median)
var userAgent = context.Request.Headers[HeaderNames.UserAgent].ToString();
// Check voor GoNative of Median user agents
if (!string.IsNullOrEmpty(userAgent)
&& (userAgent.Contains("median", StringComparison.InvariantCultureIgnoreCase)
|| userAgent.Contains("gonative", StringComparison.InvariantCultureIgnoreCase))
&& refreshToken != null)
{
// Mobile apps hebben refresh tokens nodig voor offline scenarios
await cache.ClearAsync($"refresh_token_{userId}");
await cache.SetAsync($"refresh_token_{userId}", refreshToken, TokenSettings.RefreshTokenLifeSpan);
}
}
return;
};
});
📱 Mobile App Considerations:
- User Agent Detection: GoNative/Median apps hebben specifieke user agents
- Refresh Token Storage: Mobile apps hebben langere offline periods
- Extended Lifespans: Refresh tokens langer geldig voor mobile
- Background Refresh: Apps kunnen tokens refreshen via background tasks
7. Security Middleware & Additional Services
Naast de core authentication configureren we additional services voor database access, messaging, en background tasks.
// Database context voor gateway-specifieke data
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// Real-time messaging met SignalR
services.AddSignalR();
// Message broker voor async communication
services.AddRabbitMq(Configuration.GetConnectionString("RabbitMqConnection"),
new DefaultDeadLetterConfiguration());
// Custom services
services.AddSingleton<ISignalRMessageService, SignalRMessageService>();
services.AddSingleton<ICorsHeaderService, CorsHeaderService>();
services.AddHostedService<NotificationSubscriber>();
// Data protection key storage in database
services.AddScoped<IXmlRepository, SqlXmlRepository>();
}
// Configure method: HTTP request pipeline
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Health checks voor Kubernetes/monitoring
app.UseHealthChecks("/health");
// Structured logging met request enrichment
app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms for {XForwardedFor}";
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("XForwardedFor", httpContext.Request.Headers["X-Forwarded-For"]);
};
});
// Load balancer support
app.UseForwardedHeaders();
// Environment-specific middleware
if (env.IsDevelopment() || env.EnvironmentName == "k8s-test")
{
app.UseDeveloperExceptionPage();
}
else
{
if (!Configuration.DisableHttps())
{
app.UseHsts(); // HTTPS Strict Transport Security
}
}
// KRITIEK: Security headers middleware
app.UseMiddleware<SecurityHeadersMiddleware>();
// Root path redirect naar index
app.Use(async (context, next) =>
{
if (context.Request.Path.Value == "/")
{
context.Response.Redirect("/index");
return;
}
await next.Invoke();
});
// CORS handling voor SignalR
app.UseMiddleware<SignalRCorsMiddleware>();
// Multi-tenancy middleware
app.UseActiveCompanyValidation();
// Standard ASP.NET Core pipeline
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// Endpoints mapping
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<SignalRService>("/notifications");
});
app.UseStaticFiles();
8. Ocelot Gateway Integration
Ocelot fungeert als onze API Gateway en routeert requests naar de juiste microservices. De JwtHandler injecteert automatisch de cached tokens in downstream requests.
// Development tooling
if (Configuration.IsSwaggerEnabled())
{
app.UseSwagger();
app.UseSwaggerForOcelotUI(opt =>
{
opt.PathToSwaggerGenerator = "/swagger/docs";
}, uiOpt =>
{
uiOpt.DocumentTitle = "Gateway documentation";
uiOpt.InjectStylesheet("/css/custom-swagger-ui.css");
uiOpt.InjectJavascript("/js/custom-swagger-ui.js");
});
}
// Ocelot configuration en middleware
var configuration = new OcelotPipelineConfiguration();
app.UseWebSockets(); // WebSocket support voor real-time features
app.UseOcelot(configuration).Wait(); // API Gateway functionality
// Background services
var scopedServiceProvider = app.ApplicationServices.CreateScope().ServiceProvider;
Timer timer = new Timer(new TimerCallback(CallCms), scopedServiceProvider,
TimeSpan.Zero, TimeSpan.FromHours(4)); // CMS cache refresh elke 4 uur
// Database migrations
if (Assembly.GetEntryAssembly()?.GetName().Name != "dotnet-swagger")
{
RunMigration(scopedServiceProvider);
}
}
JwtHandler Implementation
De JwtHandler is een custom Ocelot DelegatingHandler die automatisch JWT tokens uit Redis cache haalt en injecteert in downstream API calls.
// Gateway/Handlers/JwtHandler.cs
public class JwtHandler : DelegatingHandler
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<JwtHandler> _logger;
public JwtHandler(IServiceProvider serviceProvider, ILogger<JwtHandler> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
var httpContextAccessor = scope.ServiceProvider.GetService<IHttpContextAccessor>();
var cache = scope.ServiceProvider.GetService<ICacheProvider>();
if (httpContextAccessor?.HttpContext?.User?.Identity?.IsAuthenticated == true)
{
try
{
// Extract user ID van authenticated user
var userId = httpContextAccessor.HttpContext.User
.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (!string.IsNullOrEmpty(userId))
{
// Haal JWT token uit Redis cache
var token = await cache.GetAsync<string>($"access_token_{userId}");
if (!string.IsNullOrEmpty(token))
{
// Inject Bearer token in Authorization header
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
_logger.LogDebug("Injected JWT token for user {UserId}", userId);
}
else
{
_logger.LogWarning("No cached token found for user {UserId}", userId);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to inject JWT token");
// Continue zonder token - downstream service moet 401 returnen
}
}
return await base.SendAsync(request, cancellationToken);
}
}
🎯 JwtHandler Voordelen:
- Transparent: Frontend hoeft geen tokens te beheren
- Automatic: Alle API calls krijgen automatisch JWT tokens
- Cached: High-performance Redis lookups
- Secure: Tokens nooit exposed aan frontend
- Resilient: Graceful failure als tokens expired zijn
9. Mobile App Considerations
De websites ondersteunen zowel web als mobile clients. Mobile apps (GoNative/Median) hebben specifieke requirements die we elegant afhandelen in onze BFF architectuur.
User Agent Detection
// Mobile app detection in token caching
var userAgent = context.Request.Headers[HeaderNames.UserAgent].ToString();
// GoNative is het oude platform, Median is de nieuwe naam
if (!string.IsNullOrEmpty(userAgent)
&& (userAgent.Contains("median", StringComparison.InvariantCultureIgnoreCase)
|| userAgent.Contains("gonative", StringComparison.InvariantCultureIgnoreCase))
&& refreshToken != null)
{
// Mobile apps krijgen refresh tokens voor offline scenarios
await cache.ClearAsync($"refresh_token_{userId}");
await cache.SetAsync($"refresh_token_{userId}", refreshToken, TokenSettings.RefreshTokenLifeSpan);
}
📱 Mobile-Specific Challenges:
- Offline Usage: Apps kunnen tijdelijk offline zijn
- Background Refresh: Tokens moeten in background gerefreshed kunnen worden
- Platform Changes: GoNative → Median naming transition
- Extended Lifespans: Mobile users verwachten minder frequent inloggen
- Network Reliability: Mobile netwerken zijn minder betrouwbaar
Token Lifecycle Management
// TokenSettings configuratie voor verschillende client types
public static class TokenSettings
{
// Standard web app lifespans
public static readonly TimeSpan TokenLifeSpan = TimeSpan.FromHours(1);
public static readonly TimeSpan CookieLifeSpan = TimeSpan.FromHours(8);
// Extended lifespans voor mobile apps
public static readonly TimeSpan RefreshTokenLifeSpan = TimeSpan.FromDays(30); // Mobile refresh
// Security settings
public static readonly TimeSpan MaxInactivityTime = TimeSpan.FromHours(2);
public static readonly TimeSpan AbsoluteTimeout = TimeSpan.FromHours(12);
}
// Mobile refresh token endpoint
[HttpPost("api/auth/mobile/refresh")]
[Authorize]
public async Task<IActionResult> RefreshMobileToken()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userId))
{
return Unauthorized();
}
var userAgent = Request.Headers[HeaderNames.UserAgent].ToString();
var isMobileApp = !string.IsNullOrEmpty(userAgent) &&
(userAgent.Contains("median", StringComparison.InvariantCultureIgnoreCase) ||
userAgent.Contains("gonative", StringComparison.InvariantCultureIgnoreCase));
if (!isMobileApp)
{
return BadRequest("Only mobile apps can use this endpoint");
}
try
{
var refreshToken = await _cache.GetAsync<string>($"refresh_token_{userId}");
if (string.IsNullOrEmpty(refreshToken))
{
return Unauthorized("No refresh token available");
}
// Call OpenIdDict token endpoint
var tokenResponse = await RefreshTokenWithIdentityProvider(refreshToken);
if (tokenResponse != null)
{
// Update cached tokens
await _cache.SetAsync($"access_token_{userId}", tokenResponse.AccessToken,
TokenSettings.TokenLifeSpan);
if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
{
await _cache.SetAsync($"refresh_token_{userId}", tokenResponse.RefreshToken,
TokenSettings.RefreshTokenLifeSpan);
}
return Ok(new { success = true, expiresIn = tokenResponse.ExpiresIn });
}
return Unauthorized("Token refresh failed");
}
catch (Exception ex)
{
_logger.LogError(ex, "Mobile token refresh failed for user {UserId}", userId);
return StatusCode(500, "Token refresh failed");
}
}
10. Production Lessons Learned
Na maanden in productie hebben we waardevolle lessons learned die kunnen helpen bij je eigen BFF implementatie.
✅ Wat werkte goed:
- Token beveiliging: Geen JWT token exposure incidenten
- Redis Performance: Sub-millisecond token lookups
- Multi-Client Support: naadloze web + mobile ervaring
- Deployment versimpeling: Containers deploy soepel
- Monitoring: Uitstekende observability met Serilog + Application Insights
⚠️ Te vermijden valkuilen:
- Cookie Domein problemen: Zorgvuldig testen over de subdomeinen
- Redis Clustering: Zorg voor consistente hashing configuratie
- Token Cleanup: Implementeer een goede garbage collection
- Mobile User Agents: Houd user agent detectie up-to-date
- CORS Configuratie: Strict maar niet te restrictief
Performance Metrics
| Metric | Voor BFF | Met BFF | Verbetering |
|---|---|---|---|
| Authentication Flow | ~3.2 seconds | ~1.8 seconds | 44% sneller |
| API Request Latency | ~250ms | ~180ms | 28% sneller |
| beveiligingsincidenten | 2-3 per maand | 0 per maand | 100% reductie |
| Gebruikerservaringsscore | 7.2/10 | 8.9/10 | 24% Verbetering |
Operationele Best Practices
// Background cleanup job voor expired tokens
public class TokenCleanupService : IHostedService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<TokenCleanupService> _logger;
private Timer? _timer;
public async Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(CleanupExpiredTokens, null,
TimeSpan.Zero, // Start immediately
TimeSpan.FromHours(1)); // Run every hour
}
private async void CleanupExpiredTokens(object? state)
{
using var scope = _serviceProvider.CreateScope();
var cache = scope.ServiceProvider.GetService<ICacheProvider>();
try
{
// Redis SCAN voor alle access_token keys
var expiredKeys = await cache.ScanKeysAsync("access_token_*");
var deletedCount = 0;
foreach (var key in expiredKeys)
{
var exists = await cache.ExistsAsync(key);
if (!exists)
{
deletedCount++;
}
}
_logger.LogInformation("Token cleanup completed: {DeletedCount} expired tokens removed",
deletedCount);
}
catch (Exception ex)
{
_logger.LogError(ex, "Token cleanup failed");
}
}
}
// Monitoring en alerting
public class AuthenticationMetrics
{
private readonly ILogger<AuthenticationMetrics> _logger;
public void LogAuthenticationSuccess(string userId, string userAgent)
{
_logger.LogInformation("Authentication successful for user {UserId} with agent {UserAgent}",
userId, userAgent);
}
public void LogAuthenticationFailure(string reason, string? userId = null)
{
_logger.LogWarning("Authentication failed: {Reason} for user {UserId}",
reason, userId ?? "unknown");
}
public void LogTokenCacheHit(string userId)
{
_logger.LogDebug("Token cache hit for user {UserId}", userId);
}
public void LogTokenCacheMiss(string userId)
{
_logger.LogWarning("Token cache miss for user {UserId} - token may be expired", userId);
}
}
Conclusie
Onze BFF identity architectuur heeft bewezen een robuuste, veilige en schaalbare oplossing te zijn voor moderne application security. Door OpenIdDict, Ocelot Gateway en Azure Redis intelligent te combineren, hebben we een systeem gebouwd dat zowel excellent security als outstanding user experience biedt.
🎯 Key Success Factors van onze BFF Architectuur:
JWT tokens volledig server-side
Redis cache lookups
Web + Mobile seamless
Maanden stabiel in productie
🚀 implementatie Roadmap:
- Setup OpenIdDict identity provider met SQL Server
- Configureer Ocelot Gateway met authentication middleware
- Implementeer Token Caching met Azure Redis Cache
- Security Middleware Toevoegen voor headers en CSRF protectie
- Creëren JwtHandler voor automatic token injection
- Test Mobile Scenarios met refresh token flows
- Setup Monitoring met structured logging
- Deploy & Monitor productie performance
Deze architectuur representeert de state-of-the-art in enterprise identity management en toont hoe moderne security niet ten koste hoeft te gaan van developer productivity of user experience. De combination van proven Microsoft technologies με thoughtful architectural patterns maakt het een solid foundation voor any enterprise application.