Skip to content

Latest commit

 

History

History
168 lines (127 loc) · 5.86 KB

File metadata and controls

168 lines (127 loc) · 5.86 KB

PS-Asserted Access (Three-Party)

Live demo | Access Mode Comparison

Overview

The resource doesn't handle authorization itself — it delegates to the agent's Person Server. The resource issues a resource token; the agent exchanges it at the PS for an auth token; then presents the auth token back. Requires sig=jwt signing mode.

Sequence Diagram

sequenceDiagram
    participant Agent
    participant Resource
    participant PS as Person Server
    Agent->>Resource: GET /data (signed, sig=jwt with agent token)
    Resource-->>Agent: 401 + resource token (aud=PS)
    Agent->>PS: POST /token (signed, resource token in body)
    PS-->>Agent: 200 + auth token (aa-auth+jwt)
    Agent->>Resource: GET /data (signed, sig=jwt with auth token)
    Resource-->>Agent: 200 OK
Loading

Code Example

Automatic handling with AAuthClientBuilder:

using AAuth.Agent;
using AAuth.Crypto;
using AAuth;

// Hosted service: self-issue (no AP needed)
var key = AAuthKey.Generate();

using var client = AAuthClientBuilder.SelfIssuing(key)
    .As("https://my-service.example", "aauth:my-service@my-service.example")
    .WithKid("svc-key-1")
    .WithPersonServer("https://ps.example")
    .WithChallengeHandling()
    .Build();

var response = await client.GetAsync("https://resource.example/data");
// ChallengeHandler intercepts the 401, exchanges the resource token,
// swaps to the auth token, and retries automatically.
CLI/Desktop Agent (AP Enrollment)

For agents without a stable URL, enrol with an Agent Provider:

using AAuth.Agent;
using AAuth.Crypto;
using AAuth;

var keyStore = FileKeyStore.Default();
var localKeyHandle = configuration["AAuth:LocalKeyHandle"]!;
var key = await keyStore.LoadAsync(localKeyHandle)
    ?? throw new InvalidOperationException("Key not found. Run enrollment first.");
var apRefreshEndpoint = configuration["AAuth:ApRefreshEndpoint"]!;

using var client = new AAuthClientBuilder(key)
    .WithTokenRefresh(AgentProviderTokenRefresher.Create(apRefreshEndpoint, localKeyHandle)
        .WithKeyStore(keyStore)
        .Build())
    .WithChallengeHandling(personServer: "https://ps.example")
    .Build();

var response = await client.GetAsync("https://resource.example/data");
Manual Setup (Advanced)

This shows the internal handler pipeline for educational purposes. Use WithTokenRefresh + WithChallengeHandling in production code.

using AAuth.Agent;
using AAuth.Crypto;
using AAuth.Discovery;
using AAuth.HttpSig;

var keyStore = FileKeyStore.Default();
var key = await keyStore.LoadAsync(configuration["AAuth:LocalKeyHandle"]!);
var agentToken = "..."; // acquired via AP refresh endpoint
var tokenHolder = new AAuthTokenHolder(agentToken);

var signingHandler = new AAuthSigningHandler(key!,
    new JwtSignatureKeyProvider(() => tokenHolder.Current))
{
    InnerHandler = new HttpClientHandler()
};

var signedClient = new HttpClient(signingHandler);
var metadata = new MetadataClient(new HttpClient());
var exchange = new TokenExchangeClient(signedClient, metadata);

var challengeHandler = new ChallengeHandler(
    exchange, tokenHolder, "https://ps.example")
{
    InnerHandler = signingHandler
};

using var client = new HttpClient(challengeHandler);
var response = await client.GetAsync("https://resource.example/data");
```

DI Registration

using AAuth.Agent;
using AAuth.Crypto;

var keyStore = FileKeyStore.Default();
var localKeyHandle = configuration["AAuth:LocalKeyHandle"]!;
var key = await keyStore.LoadAsync(localKeyHandle);
var apRefreshEndpoint = configuration["AAuth:ApRefreshEndpoint"]!;

builder.Services.AddAAuthAgent("ps-asserted", options =>
{
    options.Key = key!;
    options.PersonServer = "https://ps.example";
    options.TokenRefresher = AgentProviderTokenRefresher.Create(apRefreshEndpoint, localKeyHandle)
        .WithKeyStore(keyStore)
        .Build();
});

This registers a named HttpClient with signing + automatic challenge handling. Inject via IHttpClientFactory.CreateClient("ps-asserted"). The ChallengeHandler intercepts 401 responses, exchanges the resource token at the PS, and retries transparently.

See Dependency Injection for full options reference.

Token Flow

  1. Agent sends signed request with agent token → resource returns 401 + resource token
  2. ChallengeHandler intercepts: extracts resource token from response
  3. TokenExchangeClient.ExchangeAsync() posts resource token to PS's token_endpoint
  4. PS verifies the resource token (TokenVerifier.VerifyResourceTokenAsync: typ/dwk/signature via the issuing resource's JWKS, exp/iat, aud, agent, agent_jkt), then verifies the agent identity, checks consent, and returns the auth token
  5. AAuthTokenHolder is updated with the auth token
  6. ChallengeHandler retries the original request (now with auth token in Signature-Key)

Autonomous vs Deferred

  • Autonomous: PS has standing consent → returns auth token immediately (step 3→4)
  • Deferred: PS requires user approval → returns 202 + pending URL → agent polls (see Deferred Consent)

Error Scenarios

Status Header/Token Cause
401 Signature-Error: invalid_signature Signature doesn't verify at resource
401 Resource token with error claim Resource rejects agent identity
403 Auth token denied PS issued auth token but resource policy still denies
202 Pending URL from PS Deferred consent — user approval required

Further Reading