Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,25 @@ var response = await client.GetAsync("https://resource.example/data");

### Three-Party Flow (Agent → Resource → Person Server)

Enrollment is a one-time provisioning step (like a DB migration). The durable signing key lives in a keystore and is referenced by ID — never extracted. The agent token is short-lived and refreshed automatically:
Hosted services self-issue agent tokens (no external AP needed). CLI/desktop agents enrol with an Agent Provider instead.

```csharp
using AAuth.Agent;
using AAuth.Crypto;
using AAuth.HttpSig;
using AAuth.Tokens;

// The key lives in the keystore; load it by the ID assigned during enrollment
var keyStore = KeyStore.Default(); // ~/.aauth/keys/ (or plug in HSM/TPM/Key Vault)
var key = await keyStore.LoadAsync("my-agent-key");
// Hosted service: generate key at startup, self-issue tokens
var key = AAuthKey.Generate();

using var client = new AAuthClientBuilder(key!)
.WithTokenRefresh(async (ctx, ct) =>
await new AgentProviderClient(new HttpClient(), keyStore)
.RefreshAsync("https://ap.example/refresh", ctx.KeyId, ct))
using var client = new AAuthClientBuilder(key)
.WithTokenRefresh(async (ctx, ct) => new AgentTokenBuilder
{
Issuer = "https://my-service.example",
Subject = "aauth:my-service@my-service.example",
KeyId = "svc-key-1",
Key = key,
PersonServer = "https://ps.example",
}.Build())
.WithChallengeHandling("https://ps.example")
.Build();

Expand Down Expand Up @@ -135,7 +140,7 @@ dotnet test tests/AAuth.Conformance # spec conformance suite only
|------|-------------|
| [src/AAuth/](src/AAuth/) | AAuth SDK library (the NuGet package) |
| [docs/](docs/) | SDK documentation — signing modes, workflows, server guides |
| [samples/](samples/) | Sample applications — WhoAmI, AgentConsole, MockPersonServer, MockAgentProvider, GuidedTour, SampleApp |
| [samples/](samples/) | Sample applications — WhoAmI, Orchestrator, AgentConsole, MockPersonServer, MockAgentProvider, GuidedTour, SampleApp |
| [tests/](tests/) | Unit, integration, and spec-conformance tests |
| [aauth-spec/](aauth-spec/) | Protocol specifications (draft-01) from [dickhardt/AAuth](https://github.com/dickhardt/AAuth) |

Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ This is the documentation for the AAuth .NET SDK (`AAuth` NuGet package). It cov
| `JwksUriSignatureKeyProvider` | `sig=jwks_uri` — JWKS-discoverable identity |
| `JwtSignatureKeyProvider` | `sig=jwt` — agent/auth token inline |
| `JktJwtSignatureKeyProvider` | `sig=jkt-jwt` — key rotation mode |
| `BootstrapBuilder` | Fluent builder for bootstrap/enrollment flows |
| `BootstrapBuilder` | Fluent builder for AP enrollment (CLI/desktop agents) |
| `ChallengeHandlingOptions` | Options for automatic 401 challenge handling |
| `InteractionHandlingOptions` | Options for deferred/interaction handling |

Expand All @@ -92,7 +92,7 @@ This is the documentation for the AAuth .NET SDK (`AAuth` NuGet package). It cov
| `InteractionHandler` | `DelegatingHandler` — handles 202 deferred/interaction |
| `TokenExchangeClient` | Sends signed `POST /token` to the Person Server |
| `DeferredPoller` | Polls the pending URL until auth_token or timeout |
| `AgentProviderClient` | Enrols with an Agent Provider (`POST /enrol`) |
| `AgentProviderClient` | Enrols with an Agent Provider (CLI/desktop agents; hosted services self-issue) |
| `IKeyStore` / `InMemoryKeyStore` | Key persistence abstraction |
| `IInteractionPresenter` | Surface interaction URLs to the user |
| `IPlatformAttestor` | Platform attestation hook |
Expand Down
4 changes: 2 additions & 2 deletions docs/advanced/key-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public sealed class AzureKeyVaultStore : IKeyStore
try
{
var secret = await _client.GetSecretAsync(keyId, cancellationToken: ct);
return AAuthKey.FromJwk(secret.Value.Value);
return AAuthKey.FromJwkJson(secret.Value.Value);
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
Expand All @@ -115,7 +115,7 @@ public sealed class AzureKeyVaultStore : IKeyStore

public async Task StoreAsync(string keyId, IAAuthKey key, CancellationToken ct)
{
var jwk = ((AAuthKey)key).ExportJwk(includePrivate: true);
var jwk = ((AAuthKey)key).ToPrivateJwk().ToJsonString();
await _client.SetSecretAsync(new KeyVaultSecret(keyId, jwk), ct);
}

Expand Down
2 changes: 1 addition & 1 deletion docs/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ See [Missions](https://explorer.aauth.dev/missions/compare).

| Token | Type Header | Issued By | Purpose | SDK |
|-------|-------------|-----------|---------|-----|
| Agent Token | `aa-agent+jwt` | Agent Provider | Binds key → identity | `AgentTokenBuilder` |
| Agent Token | `aa-agent+jwt` | Agent Provider or Self | Binds key → identity | `AgentTokenBuilder` |
| Resource Token | `aa-resource+jwt` | Resource | Challenge: "get auth from my PS/AS" | `ResourceTokenBuilder` |
| Auth Token | `aa-auth+jwt` | PS or AS | Proves user authorized this agent | `AuthTokenBuilder` |

Expand Down
38 changes: 36 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,43 @@ public class MyService(IHttpClientFactory factory)
- `AAuthSigningHandler` signs the request per RFC 9421 covering `@method`, `@authority`, `@path`, and `signature-key`.
- The resource verifies the signature using the inline public key from `Signature-Key`.

## Bootstrap with an Agent Provider (Three-Party Flow)
## Self-Issued Agent Tokens (Hosted Services)

For production scenarios, agents register with an **Agent Provider (AP)** to get an identity-bound agent token. Enrollment is a **provisioning step** that runs once (in a CLI tool or setup script). The durable signing key is generated inside a keystore and never extracted — the app references it by ID. The agent token is short-lived (typically 1 hour) and refreshed automatically by the SDK.
Hosted services (web apps, APIs, orchestrators) that have a stable URL act as their own Agent Provider per spec §Self-Hosted Agents. They generate a key at startup, publish agent metadata at `/.well-known/aauth-agent.json`, and self-sign agent tokens. No external AP enrollment is needed.

```csharp
using AAuth.Crypto;
using AAuth.HttpSig;
using AAuth.Server;
using AAuth.Tokens;

var key = AAuthKey.Generate();
const string Kid = "my-service-1";
var issuer = "https://my-service.example";

// Publish agent metadata so verifiers can discover the JWKS
app.MapAAuthAgentWellKnown(new AAuthAgentMetadataOptions
{
Issuer = issuer,
SigningKeys = new Dictionary<string, AAuthKey> { [Kid] = key },
});

// Self-issue agent tokens for outbound requests
using var client = new AAuthClientBuilder(key)
.WithTokenRefresh(async (ctx, ct) => new AgentTokenBuilder
{
Issuer = issuer,
Subject = "aauth:my-service@my-service.example",
KeyId = Kid,
Key = key,
}.Build())
.WithChallengeHandling("https://ps.example")
.Build();
```

## Bootstrap with an Agent Provider (CLI / Desktop Agents)

For agents that do NOT have a stable URL (CLI tools, desktop apps, mobile apps), registration with an external **Agent Provider (AP)** provides identity and key discovery. Enrollment is a **provisioning step** that runs once (in a CLI tool or setup script). The durable signing key is generated inside a keystore and never extracted — the app references it by ID. The agent token is short-lived (typically 1 hour) and refreshed automatically by the SDK.

### Provisioning (run once per device/install)

Expand Down
40 changes: 22 additions & 18 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ All configurable options across the AAuth .NET SDK, grouped by component.
|----------|------|---------|-------------|
| `Issuer` | `string` | — (required) | HTTPS issuer URL for this resource |
| `SigningKeys` | `Dictionary<string, AAuthKey>` | `{}` | Key-id → signing key map |
| `MaxSignatureAge` | `TimeSpan` | 60 seconds | Maximum allowed age of inbound signatures |
| `MaxFutureSkew` | `TimeSpan` | 5 seconds | Future skew tolerance for signature timestamps |
| `Clock` | `Func<DateTimeOffset>?` | `null` (UtcNow) | Clock source (threaded to `AAuthVerifier`) |
| `EnableReplayDetection` | `bool` | `true` | Enable JTI-based replay detection |
| `KeyResolver` | `ISignatureKeyResolver?` | `null` | Custom key resolver (null = default) |
| `ClientName` | `string?` | `null` | Human-readable resource name |
| `ScopeDescriptions` | `Dictionary<string, string>?` | `null` | Scope → description map for metadata |
| `SignatureWindow` | `int?` | `null` | Advertised signature validity (seconds) |
| `AuthorizationEndpoint` | `string?` | `null` | AS authorization URL |
| `RevocationEndpoint` | `string?` | `null` | Revocation endpoint URL |

## Token Builders

Expand Down Expand Up @@ -179,33 +179,37 @@ Standard `DelegatingHandler` — no configurable options. Requires an `ISignatur
| Property | Type | Required | Description |
|----------|------|:--------:|-------------|
| `Key` | `IAAuthKey` | Yes | Agent signing key |
| `AgentToken` | `string?` | No | Agent token JWT (enables jwt mode) |
| `PersonServer` | `string?` | No | Person Server URL (enables challenge handling) |
| `OnInteractionRequired` | `Func<..., Task>?` | No | Callback for deferred consent interaction |
| `OnResourceInteraction` | `Func<..., Task>?` | No | Callback for resource-managed interaction |
| `OnApprovalPending` | `Func<CancellationToken, Task>?` | No | Callback during approval polling |
| `BaseAddress` | `Uri?` | No | Target resource URL |
| `SignatureKeyProvider` | `ISignatureKeyProvider?` | No | Custom signature key provider |
| `PersonServer` | `string?` | No | Person Server URL (for challenge handling) |
| `ChallengeHandling` | `bool` | No | Enable challenge handling |
| `ChallengeHandlingOptions` | `Action<ChallengeHandlingOptions>?` | No | Configure challenge handling behavior |
| `InteractionHandling` | `bool` | No | Enable interaction handling |
| `InteractionHandlingOptions` | `Action<InteractionHandlingOptions>?` | No | Configure interaction handling behavior |
| `TokenRefresher` | `ITokenRefresher?` | No | Custom token refresh logic |
| `PollingTimeout` | `TimeSpan?` | No | Max deferred polling time (default: 5 min) |
| `RefreshThreshold` | `TimeSpan?` | No | Time before expiry to trigger refresh |
| `Capabilities` | `string[]?` | No | Agent capabilities to advertise |
| `InnerHandler` | `HttpMessageHandler?` | No | Custom inner HTTP handler |
| `CallChainProvider` | `Func<string?>?` | No | Provider for upstream auth token (call chaining) |

### AAuthResourceOptions (AddAAuthResource)

| Property | Type | Required | Description |
|----------|------|:--------:|-------------|
| `Issuer` | `string` | Yes | Resource canonical URL |
| `SigningKeys` | `List<(string Kid, IAAuthKey Key)>` | Yes | Signing key pairs |
| `MaxSignatureAge` | `TimeSpan?` | No | Override verifier MaxAge |
| `EnableReplayDetection` | `bool` | No | Register `IJtiStore` (default: false) |
| `KeyResolver` | `ISignatureKeyResolver?` | No | Custom resolver |
| `SigningKeys` | `Dictionary<string, AAuthKey>` | Yes | Key-id → signing key map |
| `ClientName` | `string?` | No | Resource display name |
| `ScopeDescriptions` | `Dictionary<string, string>?` | No | Scope descriptions for metadata |
| `SignatureWindow` | `int?` | No | Advertised signature validity (seconds) |
| `AuthorizationEndpoint` | `string?` | No | AS authorization URL |
| `RevocationEndpoint` | `string?` | No | Revocation endpoint URL |

### AAuthDiscoveryOptions (AddAAuthDiscovery)

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `MetadataCacheTtl` | `TimeSpan` | 5 minutes | Metadata document cache lifetime |
| `JwksCacheTtl` | `TimeSpan` | 1 hour | JWKS cache lifetime |
| `JwksMinRefreshInterval` | `TimeSpan` | 1 minute | Minimum time between JWKS fetches (spec: ≥1 min) |
| `MetadataCacheDuration` | `TimeSpan` | 5 minutes | Metadata document cache lifetime |
| `JwksCacheDuration` | `TimeSpan` | 5 minutes | JWKS cache lifetime |

### ChallengeHandlingOptions (WithChallengeHandling)

Expand Down
Loading
Loading