feat(server): inbound service-auth verification (proposal 0014) #125

Closed
opened 2026-09-24 12:39:07 +00:00 by Grandiras · 1 comment
Owner

Part of the v0.7.0 update cycle (wave 5). AT Protocol's model for app → service calls is inter-service JWTs, revised in April 2026 (proposal 0014). The SDK can mint them but has no general verifier: feed generators, labelers and AppViews built on ATProtoNet.Server have nothing to plug in. The only verifier is Spaces-specific.

Generator (ServiceAuthGenerator)

  • aud is did#serviceId (bare DIDs are deprecated): validate it and document it.
  • iss must not carry a fragment.
  • lxm is required for XRPC calls.
  • Optional kid header (default #atproto).
  • Always set jti.

Verifier

  • A general ServiceAuthVerifier that checks:

    • an accepted-audiences set (for example {did#svc, did} during the transition);
    • lxm bound to the NSID being called;
    • a kid allow-list (default #atproto);
    • iss without a fragment;
    • exp/iat with clock skew;
    • jti required and replay-checked.

    It verifies against a cached identity and refetches once on signature failure.

  • Generalize ISpaceReplayStore into a JTI replay store with the in-memory and EF implementations.

  • ASP.NET Core: an AddAtProtoServiceAuth(...) authentication scheme (claims did, lxm, aud) and integration with the XRPC endpoint hosting, so lxm is checked against the endpoint's NSID automatically.

  • Rebuild the Spaces service-auth verifier on it. Fix the wrong "jti is optional" comment and accept the aud forms Spaces needs.

  • Docs: a "Serving XRPC to other services" section.

The old PDS-token-forwarding bearer handler is removed by the Server restructure issue; this issue provides its replacement.

CHANGELOG Added (the verifier and scheme), Changed/Breaking changes (generator validation, stricter verifier defaults).

Part of the v0.7.0 update cycle (wave 5). AT Protocol's model for app → service calls is inter-service JWTs, revised in April 2026 ([proposal 0014](https://github.com/bluesky-social/proposals/tree/main/0014-service-auth-revised)). The SDK can mint them but has no general verifier: feed generators, labelers and AppViews built on `ATProtoNet.Server` have nothing to plug in. The only verifier is Spaces-specific. ## Generator (`ServiceAuthGenerator`) - [ ] `aud` is `did#serviceId` (bare DIDs are deprecated): validate it and document it. - [ ] `iss` must not carry a fragment. - [ ] `lxm` is required for XRPC calls. - [ ] Optional `kid` header (default `#atproto`). - [ ] Always set `jti`. ## Verifier - [ ] A general `ServiceAuthVerifier` that checks: - an accepted-audiences set (for example `{did#svc, did}` during the transition); - `lxm` bound to the NSID being called; - a `kid` allow-list (default `#atproto`); - `iss` without a fragment; - `exp`/`iat` with clock skew; - **`jti` required and replay-checked**. It verifies against a cached identity and refetches once on signature failure. - [ ] Generalize `ISpaceReplayStore` into a JTI replay store with the in-memory and EF implementations. - [ ] ASP.NET Core: an `AddAtProtoServiceAuth(...)` authentication scheme (claims `did`, `lxm`, `aud`) and integration with the XRPC endpoint hosting, so `lxm` is checked against the endpoint's NSID automatically. - [ ] Rebuild the Spaces service-auth verifier on it. Fix the wrong "jti is optional" comment and accept the `aud` forms Spaces needs. - [ ] Docs: a "Serving XRPC to other services" section. The old PDS-token-forwarding bearer handler is removed by the Server restructure issue; this issue provides its replacement. CHANGELOG `Added` (the verifier and scheme), `Changed`/`Breaking changes` (generator validation, stricter verifier defaults).
Author
Owner

From #118 (pre-existing, found while refactoring the primitives): a JWT whose exp or iat falls outside years 1–9999 throws ArgumentOutOfRangeException (from DateTimeOffset.FromUnixTimeSeconds) inside SpaceTokens.Parse, where TryParse then throws instead of returning false. The same happens in DPoPProofValidator and SpaceServiceAuthVerifier. A crafted token therefore produces a 500 instead of a 401. It is not an auth bypass. The general service-auth verifier this issue builds must range-check the numeric claims and reject out-of-range values as a validation failure. Fix the three existing sites the same way (or in #133).

From #118 (pre-existing, found while refactoring the primitives): a JWT whose `exp` or `iat` falls outside years 1–9999 throws `ArgumentOutOfRangeException` (from `DateTimeOffset.FromUnixTimeSeconds`) inside `SpaceTokens.Parse`, where `TryParse` then throws instead of returning false. The same happens in `DPoPProofValidator` and `SpaceServiceAuthVerifier`. A crafted token therefore produces a 500 instead of a 401. It is not an auth bypass. The general service-auth verifier this issue builds must range-check the numeric claims and reject out-of-range values as a validation failure. Fix the three existing sites the same way (or in #133).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/ATProto.NET#125
No description provided.