Linbik.YARP 1.1.0

dotnet add package Linbik.YARP --version 1.1.0
                    
NuGet\Install-Package Linbik.YARP -Version 1.1.0
                    
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="Linbik.YARP" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Linbik.YARP" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Linbik.YARP" />
                    
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 Linbik.YARP --version 1.1.0
                    
#r "nuget: Linbik.YARP, 1.1.0"
                    
#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 Linbik.YARP@1.1.0
                    
#: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=Linbik.YARP&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Linbik.YARP&version=1.1.0
                    
Install as a Cake Tool

Linbik.YARP

YARP (Yet Another Reverse Proxy) integration for Linbik multi-service authentication.

🚀 Features

Multi-Service Token Management (v2.0+)

  • Automatic Token Injection - Inject service-specific JWT tokens into proxied requests
  • Per-Service Token Caching - Cache tokens by service package name
  • Automatic Refresh - Refresh expired tokens using refresh token
  • Authorization Code Exchange - Exchange authorization codes for tokens
  • Backward Compatibility - Supports legacy single-token mode

Legacy Features (Deprecated)

  • Single token provider (use multi-service provider)
  • Manual token management (use automatic refresh)

📦 Installation

dotnet add package Linbik.YARP

🔧 Configuration

Basic Setup

services.AddLinbikYARP(options =>
{
    options.LinbikServerUrl = "https://linbik.com";
    options.TokenEndpoint = "/oauth/token";
    options.RefreshEndpoint = "/oauth/refresh";
    options.MainServiceId = Guid.Parse("your-main-service-guid");
    options.MainServiceApiKey = "linbik_your_api_key";
    options.EnableAutomaticRefresh = true;
    options.TokenCacheExpirationMinutes = 55;  // Refresh before 60min expiration
});

For most use cases, use MapLinbikIntegrationProxy():

// In Program.cs

// 1. Add services with configuration
builder.Services.AddLinbik(builder.Configuration);
builder.Services.AddLinbikJwtAuth();

// 2. Configure integration services in appsettings.json
// See IntegrationServices Configuration section below

var app = builder.Build();

// 3. Map integration proxy endpoint
app.MapLinbikIntegrationProxy();  // Maps /{packageName}/{**path}

Endpoint Pattern: /{packageName}/{path} routes to the integration service's BaseUrl.

Example:

  • Request: GET /payment-gateway/api/charge
  • Routes to: https://payment.example.com/api/charge
  • With: Authorization: Bearer {jwt_from_cookie}

IntegrationServices Configuration

{
  "YARP": {
    "IntegrationServices": {
      "payment-gateway": {
        "BaseUrl": "https://payment.example.com"
      },
      "survey-service": {
        "BaseUrl": "https://survey.example.com"
      },
      "courier-service": {
        "BaseUrl": "https://courier.example.com"
      }
    }
  }
}
1. User authenticates → Integration tokens stored in cookies
   Cookie: integration_payment-gateway = "eyJhbG..."
   Cookie: integration_survey-service = "eyJhbG..."

2. Client request → /payment-gateway/api/charge

3. MapLinbikIntegrationProxy():
   a. Extract {packageName} from URL → "payment-gateway"
   b. Read cookie: integration_payment-gateway
   c. Lookup BaseUrl from IntegrationServices config
   d. Proxy request to BaseUrl with Authorization header

4. Target service receives:
   GET /api/charge
   Authorization: Bearer eyJhbG...

YARP Route Configuration

{
  "ReverseProxy": {
    "Routes": {
      "payment-route": {
        "ClusterId": "payment-cluster",
        "Match": {
          "Path": "/api/payment/{**catch-all}"
        },
        "Transforms": [
          {
            "RequestHeader": "X-Service-Package",
            "Set": "payment-gateway"
          }
        ]
      },
      "courier-route": {
        "ClusterId": "courier-cluster",
        "Match": {
          "Path": "/api/courier/{**catch-all}"
        },
        "Transforms": [
          {
            "RequestHeader": "X-Service-Package",
            "Set": "courier-service"
          }
        ]
      }
    },
    "Clusters": {
      "payment-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "https://payment-gateway.com"
          }
        }
      },
      "courier-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "https://courier-service.com"
          }
        }
      }
    }
  }
}

💻 Usage

1. Token Provider Interface

public interface ITokenProvider
{
    // Authorization Methods (v2.0+)
    Task<string?> GetTokenForServiceAsync(string servicePackage, HttpContext context);
    Task ExchangeAuthorizationCodeAsync(string authorizationCode, HttpContext context);
    Task RefreshTokensAsync(HttpContext context);
    void ClearTokenCache(HttpContext context);
    
    // Legacy Methods (Deprecated)
    [Obsolete] Task<string?> GetTokenAsync(HttpContext context);
}

2. Exchange Authorization Code

After OAuth callback:

[HttpGet("/oauth/callback")]
public async Task<IActionResult> Callback(string code)
{
    // Exchange code for tokens and store in session
    await _tokenProvider.ExchangeAuthorizationCodeAsync(code, HttpContext);
    
    return RedirectToAction("Dashboard");
}

3. YARP Transform for Token Injection

public class LinbikTokenTransform : RequestTransform
{
    private readonly ITokenProvider _tokenProvider;

    public LinbikTokenTransform(ITokenProvider tokenProvider)
    {
        _tokenProvider = tokenProvider;
    }

    public override async ValueTask ApplyAsync(RequestTransformContext context)
    {
        // Get target service package from request header
        var servicePackage = context.HttpContext.Request.Headers["X-Service-Package"].ToString();
        
        if (!string.IsNullOrEmpty(servicePackage))
        {
            // Get cached token for this service
            var token = await _tokenProvider.GetTokenForServiceAsync(
                servicePackage, 
                context.HttpContext
            );
            
            if (!string.IsNullOrEmpty(token))
            {
                // Inject token into Authorization header
                context.ProxyRequest.Headers.Authorization = 
                    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
            }
        }
    }
}

Register transform:

services.AddReverseProxy()
    .LoadFromConfig(Configuration.GetSection("ReverseProxy"))
    .AddTransforms(builderContext =>
    {
        builderContext.AddRequestTransform<LinbikTokenTransform>();
    });

4. Complete Example

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    // Add Linbik YARP
    services.AddLinbikYARP(options =>
    {
        options.LinbikServerUrl = "https://linbik.com";
        options.MainServiceId = Guid.Parse("main-service-guid");
        options.MainServiceApiKey = "linbik_api_key";
        options.EnableAutomaticRefresh = true;
    });
    
    // Add YARP with Linbik transforms
    services.AddReverseProxy()
        .LoadFromConfig(Configuration.GetSection("ReverseProxy"))
        .AddTransforms<LinbikTokenTransformFactory>();
    
    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromHours(1);
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
    });
}

public void Configure(IApplicationBuilder app)
{
    app.UseSession();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapReverseProxy();
        endpoints.MapControllers();
    });
}

🔄 Token Flow

Initial Authorization

User → /login → Linbik → Authorization Code → /oauth/callback
                                                    ↓
                        ExchangeAuthorizationCodeAsync()
                                                    ↓
                        Store tokens in session:
                        - RefreshToken
                        - Integrations[]:
                            - ServicePackage: "payment-gateway"
                              Token: "jwt_token_1"
                              ExpiresAt: DateTime
                            - ServicePackage: "courier-service"
                              Token: "jwt_token_2"
                              ExpiresAt: DateTime

Request Proxying

Client Request → YARP Middleware → LinbikTokenTransform
                                          ↓
                        Read X-Service-Package header
                                          ↓
                        GetTokenForServiceAsync("payment-gateway")
                                          ↓
                        Check cache: expired?
                        - No → Return cached token
                        - Yes → RefreshTokensAsync() → Return new token
                                          ↓
                        Inject Authorization: Bearer {token}
                                          ↓
                        Proxy to target service

📋 Token Cache Structure

Session storage format:

{
  "linbik_refresh_token": "refresh_abc123...",
  "linbik_refresh_expires": "2025-11-30T12:00:00Z",
  "linbik_tokens": {
    "payment-gateway": {
      "token": "eyJhbGci...",
      "expiresAt": "2025-11-01T13:00:00Z"
    },
    "courier-service": {
      "token": "eyJhbGci...",
      "expiresAt": "2025-11-01T13:00:00Z"
    }
  }
}

🔒 Security Features

Automatic Token Refresh

✅ Tokens refresh before expiration (default: 5min before)
✅ Refresh token stored securely in session
✅ Failed refresh clears cache and requires re-authentication

Session Security

✅ HttpOnly cookies prevent XSS attacks
✅ Secure flag for HTTPS-only cookies
✅ SameSite=Lax for CSRF protection
✅ Configurable session timeout (default: 1 hour)

Token Validation

✅ Expiration check before each use
✅ Service package name validation
✅ Automatic cache invalidation on errors

🎯 Use Cases

1. E-Commerce Platform

MyShop (Main Service)
  ├── Payment Gateway (Integration Service)
  ├── Courier Service (Integration Service)
  └── Notification Service (Integration Service)

User makes purchase:
1. MyShop receives payment request
2. YARP routes to /api/payment/charge
3. LinbikTokenTransform injects payment-gateway JWT
4. Payment Gateway processes payment
5. MyShop routes to /api/courier/ship
6. LinbikTokenTransform injects courier-service JWT
7. Courier Service creates shipment

2. Microservices Architecture

API Gateway (YARP + Linbik)
  ├── User Service (Integration Service)
  ├── Order Service (Integration Service)
  ├── Inventory Service (Integration Service)
  └── Analytics Service (Integration Service)

Each microservice gets its own JWT token with specific claims.

🔄 Migration from v1.x

// ❌ Old way (v1.x - Single token)
var token = await _tokenProvider.GetTokenAsync(context);
context.Request.Headers["Authorization"] = $"Bearer {token}";

// ✅ New way (v2.0+ - Per-service tokens)
var token = await _tokenProvider.GetTokenForServiceAsync("payment-gateway", context);
context.Request.Headers["Authorization"] = $"Bearer {token}";

// Even better: Use YARP transform (automatic)
// No manual token management needed!

📖 Documentation

📄 License

This library is currently a work in progress and is not ready for production use.

Contact: info@linbik.com


Version: 2.0.0 (Multi-Service Token Management)
Last Updated: 1 Kasım 2025

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  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
1.1.0 375 12/5/2025
1.0.1 210 12/14/2025
0.46.0 343 11/30/2025
0.45.12 346 11/30/2025
0.45.7 220 8/31/2025
0.45.6 188 8/31/2025
0.45.5 226 8/27/2025
0.44.0 251 8/24/2025
0.42.0 111 8/23/2025
0.41.0 117 8/15/2025
0.40.0 174 8/14/2025
0.39.0 175 8/14/2025
0.38.0 182 8/14/2025
0.37.0 373 7/25/2025
0.36.0 549 7/22/2025
0.1.0 534 7/21/2025
0.1.0-ci0025 548 7/22/2025
0.0.1 179 6/16/2025