L402Server.AspNetCore 0.2.0

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

L402Server.AspNetCore

Discord

NuGet License: MIT

ASP.NET Core middleware for L402. Drop one line into any ASP.NET API to charge per-request Lightning payments. Built on L402Server — Lightning Enable handles invoices, macaroons, and payment verification; your API stays where it is.

Install

dotnet add package L402Server.AspNetCore

Target: .NET 8.0+.

30-second example

using L402Server.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddL402AspNetCore(opts =>
{
    opts.ApiKey = builder.Configuration["LightningEnable:ApiKey"]!;
});

var app = builder.Build();
app.UseRouting();
app.UseL402();
app.MapControllers();
app.Run();

Then mark any controller action with [L402(PriceSats = 100)] and that endpoint is gated behind a 100-sat L402 payment:

[ApiController]
[Route("api/premium")]
public class PremiumController : ControllerBase
{
    [HttpGet("weather")]
    [L402(PriceSats = 100)]
    public IActionResult Weather() => Ok(new { temp = 72 });
}

That's it. The middleware handles:

  • Issuing 402 Payment Required with a Lightning invoice on unauthenticated requests
  • Verifying Authorization: L402 <macaroon>:<preimage> on retries
  • Stashing the verified credential in HttpContext.Items[L402HttpContextKeys.VerificationResult]

Endpoints without [L402] pass through ungated.

Pricing patterns

Per-route attributes (compile-time prices)

[HttpGet("forecast"), L402(PriceSats = 100)]
public IActionResult Forecast() => Ok(...);

[HttpGet("premium-llm"), L402(PriceSats = 500, Description = "GPT-4 backed")]
public IActionResult PremiumLlm() => Ok(...);

Global flat price (gates everything mounted under the middleware)

app.UseL402(opts => opts.DefaultPriceSats = 100);

Function-form pricing (variable per request)

app.UseL402(opts =>
{
    opts.PriceSelector = ctx => ValueTask.FromResult(
        ctx.Request.Query["model"] == "premium" ? 500 : 100);
});

Resolution order: PriceSelector > [L402(PriceSats)] attribute > DefaultPriceSats. If none → request passes through ungated.

The [L402] attribute

The attribute carries three properties:

[HttpGet("forecast")]
[L402(PriceSats = 100,                       // required, ≥ 1 (compile-time constant)
      Description = "Premium forecast",     // optional, shown in the payer's wallet
      Resource = "/canonical/forecast")]    // optional, overrides the macaroon-bound resource
public IActionResult Forecast() => Ok(...);
  • PriceSats (required): price in satoshis. Attribute values are compile-time constants; for variable per-request pricing use PriceSelector on the middleware options.
  • Description (optional): embedded in the Lightning invoice.
  • Resource (optional): overrides the resource bound into the macaroon's path caveat. Defaults to the request path. Useful when several routes should share one canonical paid resource (e.g. normalizing route parameters out), since a token is only valid for the exact resource it was minted for.

The attribute can also be applied class-level to gate every action on a controller with one declaration. If both the controller and an action carry [L402], the action-level attribute wins for that endpoint (endpoint metadata is ordered least- to most-specific and the middleware reads the most specific match) — so a class-level default can be repriced per action:

[ApiController]
[Route("api/premium")]
[L402(PriceSats = 100)]   // every action below costs 100 sats
public class PremiumController : ControllerBase
{
    [HttpGet("weather")]
    public IActionResult Weather() => Ok(new { temp = 72 });

    [HttpGet("forecast")]
    public IActionResult Forecast() => Ok(new { rain = false });
}

Server-side path-caveat enforcement

Each macaroon is bound to the resource it was minted for. By default the middleware sends the resolved resource (same precedence as minting: [L402(Resource = ...)] > ResourceSelector > request path) with every verification call, so Lightning Enable rejects a token that was paid for a different path — replay protection across endpoints without writing a comparison yourself.

// To opt out (and do your own comparison against VerificationResult.Resource):
app.UseL402(opts => opts.EnforceResourceOnVerify = false);

Requires L402Server ≥ 0.2.0 (a transitive dependency of this package).

Verified credential on downstream handlers

[HttpGet("weather"), L402(PriceSats = 100)]
public IActionResult Weather(HttpContext ctx)
{
    var result = (VerificationResult)ctx.Items[L402HttpContextKeys.VerificationResult]!;
    _logger.LogInformation("Served {Resource} for {Sats} sats ({Hash})",
        result.Resource, result.AmountSats, result.PaymentHash);
    return Ok(new { temp = 72 });
}

Configuration

Option Type Default Notes
DefaultPriceSats int? null Flat price applied when no [L402] attribute is present
PriceSelector Func<HttpContext, ValueTask<int>>? null Variable pricing per request; overrides attribute + default
ResourceSelector Func<HttpContext, string>? HttpContext.Request.Path Bound as a macaroon caveat
DescriptionSelector Func<HttpContext, string?>? null Shown to the payer in their wallet
IdempotencyKeySelector Func<HttpContext, string?>? null Sends X-Idempotency-Key for retry-safe challenge minting
EnforceResourceOnVerify bool true Sends the resolved resource with verification so the producer API enforces the macaroon's path caveat server-side; set false to opt out
OnInvalidToken Func<HttpContext, VerificationResult, ValueTask>? sends 401 Custom handler — e.g. send a fresh 402 instead. Your handler must write a response or call the next middleware itself

Two integration modes

Lightning Enable supports two integration shapes:

  • Proxy mode — point Lightning Enable at your API URL; we forward authenticated requests on your behalf.
  • Native mode — install this middleware in your existing API. Lightning Enable handles payment; your API handles everything else. This middleware is the Native mode for ASP.NET Core.

Documentation →

How the protocol works under the hood

Every paid request makes one round-trip to the Lightning Enable hosted API. The middleware never holds key material, never signs macaroons, never verifies preimages locally — all of that is in the hosted backend. The middleware itself is HTTP-client glue.

What you pay for with your Lightning Enable subscription: the protocol broker that lets this be one line of middleware.

Sibling packages

  • L402Server — the SDK this middleware is built on. Use directly for non-ASP.NET-Core .NET hosts.
  • L402Requests — consumer-side HTTP client. Auto-pays L402 challenges.

Contributing

Open source under MIT. Issues and PRs welcome.

License

MIT © Refined Element

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
0.2.0 58 7/3/2026
0.1.2 73 5/13/2026
0.1.1 62 5/12/2026
0.1.0 65 5/12/2026