Pisum.Bff.Yarp 1.11.0
Pisum.Bff.Yarp
YARP (Yet Another Reverse Proxy) integration for the Pisum Backend for Frontend (BFF) security framework.
Overview
Pisum.Bff.Yarp extends the Pisum BFF framework with YARP reverse proxy capabilities, enabling automatic bearer token forwarding to downstream APIs. This package acts as an API gateway, securely proxying requests from your SPA to backend services while automatically injecting access tokens.
Key Features
- ✅ Automatic Token Forwarding - Access tokens automatically added to downstream requests
- ✅ Token Management - Handles token refresh and renewal
- ✅ Resilience - Built-in retry policies with Polly
- ✅ Configuration-Based Routing - Define routes via appsettings.json
- ✅ BFF Integration - Seamless integration with Pisum.Bff session management
- ✅ Zero Client-Side Token Handling - Tokens never exposed to the browser
Installation
From Pisum NuGet Server
dotnet add package Pisum.Bff.Yarp --source https://nuget.pisum.synology.me/v3/index.json
dotnet add package Pisum.BFF --source https://nuget.pisum.synology.me/v3/index.json
Or via Package Manager:
Install-Package Pisum.Bff.Yarp -Source https://nuget.pisum.synology.me/v3/index.json
Install-Package Pisum.BFF -Source https://nuget.pisum.synology.me/v3/index.json
Configure NuGet Source
Add the Pisum NuGet server to your NuGet configuration:
dotnet nuget add source https://nuget.pisum.synology.me/v3/index.json --name Pisum
Then install normally:
dotnet add package Pisum.Bff.Yarp
dotnet add package Pisum.BFF
Quick Start
1. Configure Services
// Program.cs
using Pisum.Bff.Endpoints;
var builder = WebApplication.CreateBuilder(args);
// Add BFF core services
builder.Services.AddBffCore(options =>
{
options.AnonymousSessionResponse = AnonymousSessionResponse.Response401;
})
.AddServerSideSessions();
// Add authentication
builder.Services.AddAuthentication(/* ... */);
// Add YARP with BFF integration
builder.Services.AddBffYarp(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
2. Configure Middleware
// Middleware pipeline
app.UseSession();
app.UseAuthentication();
app.UseBffMiddleware();
app.UseAuthorization();
// Map BFF endpoints
app.MapBffManagementEndpoints();
// Map reverse proxy (automatic token forwarding)
app.MapReverseProxy();
app.Run();
3. Configure Routes (appsettings.json)
{
"ReverseProxy": {
"Routes": {
"api-route": {
"ClusterId": "api-cluster",
"Match": {
"Path": "/api/{**catch-all}"
}
},
"graphql-route": {
"ClusterId": "graphql-cluster",
"Match": {
"Path": "/graphql/{**catch-all}"
}
}
},
"Clusters": {
"api-cluster": {
"Destinations": {
"destination1": {
"Address": "https://api.example.com/"
}
}
},
"graphql-cluster": {
"Destinations": {
"destination1": {
"Address": "https://graphql.example.com/"
}
}
}
}
}
}
How It Works
Request Flow
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ SPA Client │ │ BFF Server │ │ Backend API │
│ (Browser) │ │ (YARP) │ │ │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ 1. API Request │ │
│ (with cookie) │ │
├─────────────────────>│ │
│ │ │
│ │ 2. Validate session │
│ │ Get access token │
│ │ │
│ │ 3. Forward request │
│ │ + Bearer token │
│ ├─────────────────────>│
│ │ │
│ │ 4. API Response │
│ │<─────────────────────┤
│ │ │
│ 5. Return response │ │
│<─────────────────────┤ │
│ │ │
Key Security Features:
- Client sends request with httpOnly cookie (no token)
- BFF validates session and retrieves access token
- YARP adds
Authorization: Bearer <token>header - Backend API receives authenticated request
- Response returned to client
Configuration
Basic YARP Configuration
{
"ReverseProxy": {
"Routes": {
"route-name": {
"ClusterId": "cluster-name",
"Match": {
"Path": "/api/{**catch-all}"
},
"Transforms": [
{
"RequestHeader": "X-Custom-Header",
"Set": "custom-value"
}
]
}
},
"Clusters": {
"cluster-name": {
"Destinations": {
"destination1": {
"Address": "https://backend-api.com/"
}
},
"HttpClient": {
"MaxConnectionsPerServer": 100
}
}
}
}
}
Load Balancing
{
"Clusters": {
"api-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"api1": { "Address": "https://api1.example.com/" },
"api2": { "Address": "https://api2.example.com/" },
"api3": { "Address": "https://api3.example.com/" }
}
}
}
}
Health Checks
{
"Clusters": {
"api-cluster": {
"HealthCheck": {
"Active": {
"Enabled": true,
"Interval": "00:00:10",
"Timeout": "00:00:05",
"Policy": "ConsecutiveFailures",
"Path": "/health"
}
},
"Destinations": {
"api1": { "Address": "https://api.example.com/" }
}
}
}
}
Circuit Breaker with Polly
The package includes built-in Polly retry policies:
// Automatic retry with exponential backoff
// Configured automatically when using AddBffYarp()
// Custom configuration (advanced)
builder.Services.AddBffYarp(
builder.Configuration.GetSection("ReverseProxy"),
options =>
{
options.MaxRetries = 3;
options.BackoffMultiplier = 2;
}
);
Advanced Scenarios
Custom Transformations
// Add custom request transformations
builder.Services.AddBffYarp(builder.Configuration.GetSection("ReverseProxy"))
.AddTransforms(context =>
{
context.AddRequestHeader("X-Forwarded-User",
context.HttpContext.User.Identity?.Name ?? "anonymous");
});
Route-Specific Configuration
{
"Routes": {
"admin-api": {
"ClusterId": "admin-cluster",
"Match": {
"Path": "/admin/{**catch-all}"
},
"AuthorizationPolicy": "AdminOnly",
"RateLimiterPolicy": "AdminRateLimit"
}
}
}
WebSocket Support
{
"Routes": {
"websocket-route": {
"ClusterId": "websocket-cluster",
"Match": {
"Path": "/ws/{**catch-all}"
},
"WebSocketOptions": {
"Enabled": true
}
}
}
}
Token Management
Automatic Token Refresh
The BFF automatically handles token refresh:
// Configure token management
builder.Services.AddAuthentication()
.AddOpenIdConnect(options =>
{
options.SaveTokens = true; // Required for token forwarding
options.Scope.Add("offline_access"); // For refresh tokens
});
// Tokens are automatically refreshed before expiry
// No client-side code needed
Manual Token Access
// Access tokens programmatically
public class MyService
{
private readonly IHttpContextAccessor _contextAccessor;
public MyService(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public async Task<string?> GetAccessTokenAsync()
{
var context = _contextAccessor.HttpContext;
var token = await context.GetTokenAsync("access_token");
return token;
}
}
Performance Optimization
Connection Pooling
{
"Clusters": {
"api-cluster": {
"HttpClient": {
"MaxConnectionsPerServer": 100,
"RequestHeaderEncoding": "utf-8"
}
}
}
}
Response Caching
// Enable response caching
app.UseResponseCaching();
// Configure cacheable routes
builder.Services.AddBffYarp(config)
.AddTransforms(context =>
{
if (context.Path.StartsWithSegments("/api/cached"))
{
context.AddResponseHeader("Cache-Control", "public, max-age=300");
}
});
Monitoring and Diagnostics
Enable Detailed Logging
{
"Logging": {
"LogLevel": {
"Yarp": "Information",
"Yarp.ReverseProxy": "Debug"
}
}
}
Telemetry
// YARP includes built-in metrics
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddTelemetry();
Target Frameworks
- .NET 8.0
- .NET 9.0
Dependencies
- Yarp.ReverseProxy (2.3.0) - Reverse proxy functionality
- Polly (8.6.4) - Resilience and transient fault handling
- Pisum.Bff (1.0.0) - Core BFF functionality
Related Packages
- Pisum.Bff - Core BFF library
- Pisum.Bff.Shared - Shared models and utilities
- Pisum.Bff.Blazor.Client - Blazor WebAssembly components
Examples
See the samples directory for complete examples:
- Demo.Bff.Blazor.Server - Blazor Server with YARP
- Demo.Bff.Angular - Angular SPA with YARP
Best Practices
- Use HTTPS for all downstream services
- Configure health checks for production deployments
- Implement rate limiting to protect backend services
- Monitor proxy metrics for performance insights
- Use load balancing for high-availability scenarios
- Configure timeouts appropriately for your APIs
- Enable circuit breakers for resilient communication
Security Considerations
- Access tokens are never exposed to the client
- All token handling is done server-side
- Supports token refresh without client involvement
- CORS is handled at the BFF level
- Backend APIs receive properly authenticated requests
Troubleshooting
Common Issues
Problem: 401 Unauthorized from downstream API
- Solution: Ensure
SaveTokens = truein OIDC configuration - Solution: Verify the API expects
Bearertokens
Problem: Token not being forwarded
- Solution: Check that the route is properly configured in appsettings.json
- Solution: Ensure the user is authenticated before making the request
Problem: Connection timeouts
- Solution: Adjust
HttpClienttimeout settings in cluster configuration - Solution: Check network connectivity to downstream services
Contributing
This is part of the Pisum BFF framework. For contributions and issues, please refer to the main repository.
License
Copyright © 2025 pisum.net
Support
For questions and support, please open an issue in the main repository.
No packages depend on Pisum.Bff.Yarp.
.NET 8.0
- Pisum.BFF (>= 1.11.0)
- Polly (>= 8.6.4)
- Yarp.ReverseProxy (>= 2.3.0)
.NET 9.0
- Pisum.BFF (>= 1.11.0)
- Polly (>= 8.6.4)
- Yarp.ReverseProxy (>= 2.3.0)
| Version | Downloads | Last updated |
|---|---|---|
| 1.11.0 | 62 | 10/21/2025 |