using System.Collections.Concurrent;
using ShieldLabs;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// One client per domain, shared by every request: it is safe for concurrent use.
var shieldlabs = new ShieldLabsClient(new ShieldLabsClientOptions
{
ApiKey = Environment.GetEnvironmentVariable("SHIELDLABS_API_KEY"), // sec_your_private_key
});
var webhookSecret = Environment.GetEnvironmentVariable("SHIELDLABS_WEBHOOK_SECRET") ?? ""; // whsec_your_signing_secret
// One identification authorizes one action. This in-memory store keeps the example short; in
// production, claim request IDs atomically in a shared store (Redis SET NX, a unique key).
var usedRequestIds = new ConcurrentDictionary<string, DateTimeOffset>();
// 1. The page posts the requestId it received from the browser agent with the signup form.
app.MapPost("/signup", async (SignupForm form, CancellationToken cancellationToken) =>
{
Identification? identification;
try
{
// Scoring is asynchronous: this polls until the verdict is stored (10-second budget by default).
identification = await shieldlabs.Identifications.GetAsync(form.RequestId ?? "", cancellationToken: cancellationToken);
}
catch (ValidationException)
{
return Results.BadRequest(new { error = "requestId must be a UUID" });
}
catch (ShieldLabsException)
{
// Network, key or rate-limit problem: the action stays unverified.
return Results.Json(new { error = "unverified" }, statusCode: 503);
}
// 2. Missing, reused, stale, rate-limited, automated or dangerous: refuse.
var firstUse = identification is not null && usedRequestIds.TryAdd(identification.RequestId, DateTimeOffset.UtcNow);
var evaluation = Risk.Evaluate(identification, new EvaluateOptions { IsReplay = _ => !firstUse });
if (!evaluation.Ok)
{
return Results.Json(new { error = "refused", reason = evaluation.Reason }, statusCode: 403);
}
// Create the account here.
return Results.Ok(new { ok = true, band = evaluation.Band });
});
// 3. Verify each webhook delivery over the raw body before reading it.
app.MapPost("/webhooks/shieldlabs", async (HttpRequest request) =>
{
using var body = new MemoryStream();
await request.Body.CopyToAsync(body);
WebhookEvent evt;
try
{
evt = WebhookEvents.ConstructEvent(body.ToArray(), request.Headers[WebhookSignature.HeaderName], webhookSecret);
}
catch (SignatureVerificationException)
{
return Results.Unauthorized();
}
catch (WebhookParseException)
{
return Results.BadRequest();
}
if (evt is IdentificationScoredEvent scored)
{
// Handle each request ID once: future retries resend identical bytes.
app.Logger.LogInformation("{RequestId}: risk score {RiskScore}", scored.Data.RequestId, scored.Data.RiskScore);
}
return Results.Ok();
});
app.Run();
record SignupForm(string? RequestId, string? Email);