Pisum.Bff.Yarp 1.11.0

Pisum.Bff.Yarp

NuGet License

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:

  1. Client sends request with httpOnly cookie (no token)
  2. BFF validates session and retrieves access token
  3. YARP adds Authorization: Bearer <token> header
  4. Backend API receives authenticated request
  5. 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

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

  1. Use HTTPS for all downstream services
  2. Configure health checks for production deployments
  3. Implement rate limiting to protect backend services
  4. Monitor proxy metrics for performance insights
  5. Use load balancing for high-availability scenarios
  6. Configure timeouts appropriately for your APIs
  7. 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 = true in OIDC configuration
  • Solution: Verify the API expects Bearer tokens

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 HttpClient timeout 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

.NET 9.0

Version Downloads Last updated
1.11.0 63 10/21/2025