Expand description
authkestra-devsig — device-bound signature authentication.
This is the fourth request-authentication family, alongside OAuth/OIDC, sessions, and Basic: per-request proof of possession of a device-bound private key, with the public key and its identity binding travelling in the request itself. Verification needs no session store, no token introspection, and no per-request network call — any service holding one cached Issuer JWKS can verify a request independently.
§The two credentials
Every request carries two JWSes that do different jobs:
X-Signature— short-lived, single-use, signed by the device. Proves possession of the private key for this request.X-Attestation— long-lived, reusable, public, signed by the Issuer. Binds that key’s RFC 7638 thumbprint (cnf.jkt) to an identity and its attributes. This is whatauthkestra-opmints at enrolment (tracked separately in authkestra#136); this crate only verifies attestations, it does not mint them.
Neither is sufficient alone: the signature without the attestation is an anonymous key, and
the attestation without the signature is exactly the bearer-token pattern this design exists
to avoid — see VerifyError::MissingCredential. Only together, and only after the
thumbprint binding is checked, do they authenticate anything. See verify’s docs for why
that check is the one step in the algorithm that cannot be skipped, reordered, or inferred
from the others.
§Entry point
verify is a plain async function, deliberately decoupled from any HTTP framework:
let identity = authkestra_devsig::verify(&request, &config, &jwks, &replay_store).await?;§Framework integration — lives in the adapter crates, per AGENTS.md
Two extension points exist in authkestra-engine today: AuthMethod (roadmapped, used only
in tests, and its AuthInput carries no HTTP request context at all) and
AuthenticationStrategy<I> (what Guard<I>/JwtStrategy actually chain, but
authenticate(&self, parts: &Parts) has no body — and this scheme needs the raw body bytes
for the bdh check). Neither can express a body-aware verifier today, and deciding which one
should grow that capability — or whether a new trait should — is the framework’s own
architecture call, tracked in
authkestra#137.
This crate stays framework-agnostic (per AGENTS.md’s “Framework Agnostic” rule) and exposes
only the plain verify function plus the types it needs. Framework wiring lives in the
adapter crates instead:
authkestra-axum’sdevsigfeature (see itsdevsigmodule) ships atower::Layerthat buffers and hashes the body ahead of axum’s extraction and injects a verifiedDeviceIdentityinto request extensions, plus a thinFromRequestPartsextractor that reads it back out.authkestra-actix’sdevsigfeature (see itsdevsigmodule) ships the equivalentactix_web::dev::Transformmiddleware andFromRequestextractor.
Both exist specifically because they need neither authkestra-engine trait to exist yet, and
migrating to whichever trait the maintainer lands on is a matter of swapping which caller
builds a SignedRequest and calls verify — the algorithm itself is unaffected either
way. See each adapter crate’s devsig module docs for why an AuthenticationStrategy<I>
impl is deliberately not provided today.
Re-exports§
pub use builder::DevSig;pub use builder::DevSigBuilder;
Modules§
Structs§
- Device
Identity - Identity established by a request that passed every step of the verification algorithm.
- InMemory
Replay Store - A
HashMap-backedReplayStorefor tests and single-process deployments. - Issuer
Jwks - An in-memory cache of Issuer public keys, keyed by
(issuer, kid). - Signed
Request - The two credential headers plus the request facts needed to check request binding.
- Unavailable
Replay Store - A
ReplayStorethat always fails — for exercising the fail-closed path in tests (a replay store outage must still reject). - Verifier
Config - Static configuration for
crate::verify.
Enums§
- Replay
Error - Failure modes for a replay store. A request must fail closed on any
Errhere — not justReplayError::Unavailable— meaningverify()treats a store outage identically to a genuine replay: reject, never silently allow. - Verify
Error - Why
crate::verifyrejected a request. Never carries the raw cryptographic material that caused the rejection — only enough to log or monitor safely.
Traits§
- Replay
Store - Atomic single-use marker store, keyed by
jti.
Functions§
- verify
- Verifies a device-bound signed request against the configured trust policy.