aauth-protocol 0.1.0

AAuth (draft-hardt-oauth-aauth-protocol): HTTP message signatures, agent, person and auth tokens, and an agent-side Person Server client
Documentation
# aauth-protocol

[AAuth](https://github.com/dickhardt/AAuth) (`draft-hardt-oauth-aauth-protocol-11`) for Rust. AAuth is how an agent proves who it is, and what a person has allowed it to do, on every request it makes.

Each request carries an RFC 9421 HTTP Message Signature under the AAuth profile. The signer's key is named in `Signature-Key`, usually as a token that binds it: an **agent token** from the agent's provider, a **person token**, or an **auth token** from the person's Person Server.

This crate is the part of the protocol every role shares.

| Module | What it does |
| --- | --- |
| `keys`, `jwk`, `jwt` | Ed25519 signing keys, public JWKs and RFC 7638 thumbprints, compact JWS |
| `httpsig` | Sign a request, or verify one and learn which key signed it |
| `token` | Mint tokens, or verify one against its issuer's keys and the request's signer |
| `discovery` | Find an issuer's keys through its metadata document, over an HTTP client you choose |
| `resource` | The resource's side: authenticate a request, mint resource tokens, challenge |
| `requirement` | The `AAuth-Requirement` header |
| `mission`, `budget` | What a person approved, and what it may spend |
| `client` | The agent's Person Server client, and an SSRF-guarded HTTP client (`client` feature, on by default) |

The Person Server itself is the [`aauth-person-server`](https://crates.io/crates/aauth-person-server) crate.

## Sign as an agent

```rust
use aauth_protocol::httpsig::{sign_jwt, Outgoing};

let headers = sign_jwt(&session_key, &agent_token, &Outgoing {
    method: "POST",
    url: "https://api.booking.example/v1/reserve",
    content_type: None,
    body: Some(br#"{"offer_id":"fl-qf1"}"#),
    created: aauth_protocol::now_unix(),
})?;
// content-digest, content-type, signature-key, signature-input, signature
```

## Verify as a resource

```rust
use aauth_protocol::resource::authenticate;
use aauth_protocol::token::TokenKind;

let authenticated = authenticate(
    &request,                         // httpsig::Incoming: method, authority, path, headers, body
    "https://api.booking.example",    // this resource's identifier
    &[TokenKind::Auth],
    &VerifyOptions::default(),
    &resolver,                        // discovery::JwksResolver over a guarded ReqwestFetch
).await?;
authenticated.token.scope();
```

`authenticate` checks the signature first, then that the token's `cnf.jwk` is the key that signed. It checks the token against its issuer's published keys last. Nothing a token claims is believed until that last check passes.

Only the token's issuer is ever looked up, and only when it is a server identifier. A `Signature-Key: sig=jwks_uri` from an unauthenticated caller is refused, not fetched. Use `ReqwestFetch::guarded` for discovery: it calls `https` only, refuses IP literals and `localhost`, and its resolver refuses private addresses.

## Talk to a Person Server as an agent

```rust
use aauth_protocol::client::{guarded_client, Agent, Answer, DeniedNetworks, Person, PersonServerClient, Reach};

let client = PersonServerClient::new(guarded_client(DeniedNetworks::default())?, Reach::default());
let metadata = client.metadata("https://ps.acme.example").await?;
let agent = Agent { agent_token: &agent_token, key: &session_key };
let sam = Person { login_hint: Some("user:sam@acme.example"), tenant: None };
match client.propose_mission(&agent, &metadata, &json!({ "description": "Sydney, 19-23 October" }), &sam).await? {
    Answer::Pending { location, retry_after } => { /* poll `location` */ }
    Answer::Granted(mission) => { /* approved already */ }
    Answer::Denied { reason, .. } => { /* refused */ }
    _ => {}
}
```

## Interop

The tests pin this crate's signatures to [`@hellocoop/httpsig`](https://www.npmjs.com/package/@hellocoop/httpsig), by the spec's author, with fixed vectors in `tests/vectors/hellocoop.json`: requests it signed verify here, and what this crate signs is exactly what it verified.

## Status

The crate implements protocol draft `-11`.

- **Algorithms:** signing is Ed25519; verification accepts Ed25519 and ES256.
- **`Signature-Key` schemes:** `jwt`, `jwks_uri` and `hwk` are supported; `jkt-jwt` and `x509` are not.
- **Revocation:** not checked.
- **`@query`:** follows RFC 9421 and includes the leading `?`. `@hellocoop/httpsig` 2.6 leaves the `?` out, so the two disagree on any signed request that has a query string.

Until 1.0 the API may change between minor versions.

## License

Apache-2.0