Skip to content

Bring the READMEs up to v0.13 - #409

Merged
marcjazz merged 2 commits into
mainfrom
docs/readmes-for-v0.13
Sep 22, 2026
Merged

marcjazz merged 2 commits into
mainfrom
docs/readmes-for-v0.13

Conversation

@marcjazz

Copy link
Copy Markdown
Collaborator

Docs only. Pairs with the v0.13.0 release in #399.

Every install snippet was five minors stale

-authkestra = { version = "0.7", features = ["axum", "github"] }
+authkestra = { version = "0.13", features = ["axum", "github"] }

Eight occurrences across four files (README.md, actix, axum, macros). Anyone copying the quick start got a version from long before half the framework existed — the kind of error that costs a first impression rather than a debugging session.

The feature list had not kept up with what shipped

Phase 2's whole passwordless track was missing. Added:

  • Passwordless-native authentication — passkeys, TOTP, magic links, email/SMS one-time codes, recovery codes, with the last three noted as new in 0.13.
  • Re-proof gating — the thing that had to land before the other three, and which explains why this framework can't do the usual "re-enter your password" (it owns no password).

Recovery codes are described as a factor-agnostic look-up secret authenticator (NIST SP 800-63B §5.1.2) rather than "backup codes", because the distinction is the whole point: they work for an account that never enrolled TOTP.

Two published crates were missing from both tables

authkestra-store-sqlx and authkestra-store-testsuite, absent from the root README and docs/README.md alike. The second is worth listing precisely because it's what a third-party store implementor needs to know exists — it's how they prove their backend honours the traits' atomicity contracts rather than discovering it in production.

Checked, not assumed

authkestra-policy is still only re-exported through the facade and called by nothing, so both READMEs' "proof of concept — nothing calls it yet" wording stands unchanged. I verified that rather than refreshing it along with everything else.

Ordering note

This advertises 0.13 before #399 merges. If the release slips, the READMEs name a version that isn't on crates.io yet — so this wants to land with or just after #399, not well before it.

Also done, outside this PR

The GitHub About section and topics, which can't live in a commit:

  • description → Framework-agnostic authentication orchestrator for Rust — passkeys, magic links, one-time codes, OAuth2/OIDC, and a built-in OpenID Provider
  • topics → added rust, passwordless, passkeys, webauthn, mfa, totp, magic-link, openid-connect, jwt, dpop, identity, security alongside the seven that were already there.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu

marcjazz and others added 2 commits September 22, 2026 17:01
Continues #353, and not where I said it would be. `authkestra-ssf` was
next on my own list and turns out to need nothing: every rejection in
`verify_at` funnels through a `reject()` helper that logs each `SetError`
with a stable code and a catch-all arm, and reasons explicitly about not
leaking the deployment's issuer, audience or clock configuration into the
RFC 8935 failure body. The raw `Err` returns I had counted live in
private helpers whose results are wrapped with `.map_err(reject)` at the
call site. Nothing to do there.

The real remaining gap is `authkestra-resource`, which is where #353 tier
3 put it, and it is the hot path: `validate_jwt_generic` is where
`validate_jwt` and `validate_jwt_with_resolver` both converge, so every
resource server runs it per request. It had no span and six failure paths
that all arrived by `?` and emitted nothing — a rejected token said only
that it was rejected.

It now carries a span named `validate_jwt` recording `kid` and `alg`,
recorded as soon as the header is decoded so a later failure still has
them, and one outcome line covering all six. #353 cites #335's
`InvalidAlgorithm` — diagnosable only by reading `jsonwebtoken`'s source
— as what this absence costs.

`validate_jwt_with_resolver` gets its own line for `resolve_cache`: it
runs before the span exists, so `UntrustedIssuer` and `MissingIssuer`
would otherwise still be invisible, and those are exactly the
misconfigurations worth naming.

`authkestra-policy` gets two lines. Its decision path was already well
covered — loader failures, policy evaluation errors, a refused Allow and
the decision itself all say something — and the two `InvalidRequest`
returns were the only paths leaving in silence. A typo'd context key is
precisely what the schema check exists to turn into a rejection rather
than a silently unsatisfied `when` clause, so whoever hit one should be
told which key.

Four tests, and I checked they fail without the change rather than
assuming: two of them do. One asserts the token never reaches the logs,
which #353 calls out as the line any new instrumentation must hold.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu
Every install snippet pinned `version = "0.7"` while the crates are at
0.12 heading to 0.13 — five minors stale across four files, so anyone
copying the quick start got a version from long before half the
framework existed. That is the kind of error that costs a first
impression rather than a debugging session.

The feature list had also not kept up with what shipped. Phase 2's whole
passwordless track was missing: magic links, email/SMS one-time codes
and recovery codes, plus the re-proof gate that had to land before any of
them. Recovery codes are described as what they are — a factor-agnostic
look-up secret authenticator, NIST SP 800-63B §5.1.2 — rather than
"backup codes", because the distinction is the point: they work for an
account that never enrolled TOTP.

Two published crates were absent from both crate tables:
authkestra-store-sqlx and authkestra-store-testsuite. The second is
worth listing precisely because it is the thing a third-party store
implementor needs to know exists — it is how they prove their backend
honours the traits' atomicity contracts rather than discovering it in
production.

Checked rather than assumed: authkestra-policy is still only re-exported
through the facade and called by nothing, so both READMEs' "proof of
concept, nothing calls it yet" wording stands unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu
@codecov

codecov Bot commented Sep 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@marcjazz
marcjazz merged commit 37bc995 into main Sep 22, 2026
22 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 23, 2026
marcjazz added a commit that referenced this pull request Sep 23, 2026
* docs: mark Phase 2 shipped, correct RFC status lines, fix ssf/policy status

Mechanical pass ahead of a book-wide sweep for v0.13.0.

RFC-010/011/012 all said "proposed, not implemented" despite shipping
in v0.13.0 (#400, #402, #404) — the same class of staleness as the
README version pins fixed in #409, just in the status line instead of
an install snippet. Corrected to "implemented", each naming its feature
flag and module.

Phase 2's three checkboxes in roadmap.md were unchecked even though the
gate plus all three methods are merged; checked them and added a
one-line note on the two pieces of shared store infrastructure
(AtomicDecrement, the tightened CredentialStore contract) that came out
of building them, since a future feature is the likeliest reader of
that note.

`authkestra-ssf` was listed as "(planned)" in the crate-status section
and named again under Phase 4 as future work ("Implement SSF/CAEP for
real-time revocation") — but it shipped as #309, before this session's
work began. Relabelled "(shipped, receiver only)", and Phase 4 now
names precisely what the crate's own module doc says is still missing:
revoking or attenuating a live session on an event, transmitting SETs
of its own, and CAEP-aware middleware. `authkestra-policy`'s "proof of
concept, nothing calls it yet" status was checked against the source
and left unchanged — it is still true.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu

* docs: bring the mdBook up to v0.13.0

Fanned out over the book's 11 chapters (a Workflow run, one agent per
chapter plus a cross-chapter verify pass) after scouting which ones
plausibly needed anything: five had no v0.13.0-relevant content and are
untouched (ch01, ch04, ch05, ch06, ch07, ch10, ch11 — checked, not
skipped) or made no change after checking; four did.

ch08 (Getting Started Tutorial): the same `version = "0.7"` pin bug
already fixed in the top-level READMEs (#409), four occurrences, fixed
to 0.13 the same way.

ch02 (Core Engine and Identity): its KvStore/CredentialStore persistence
list was missing recovery-code credentials and the magic-link/OTP
one-time secrets — verified each against its actual store bound in
source before adding it.

ch03 (Core Traits): the chapter that documents AuthMethod and the
KvStore atomic-primitive family in depth, so it had the most real gaps.
Added the three new AuthMethod impls with their feature flags and RFC
references; added AtomicDecrement alongside AtomicConsume/AtomicInsert;
and corrected two "planned traits" bullets that flatly denied
authkestra-ssf and authkestra-policy exist, when both are real, published
crates (receiver-only and unwired-PoC respectively) — the chapter
predates both shipping.

ch09 (Security and Threat Model): added re-proof gating as a threat this
release closes — a hijacked-but-merely-logged-in session can no longer
enrol the attacker's own factor — since this chapter already enumerates
shipped security properties and re-proof gating is squarely one. The
cross-chapter verify pass then caught what no single-chapter read could:
this same chapter's CAEP section still said "no SSF/CAEP receiver...
in the workspace", flatly contradicting the ssf correction just made in
ch03 and docs/roadmap.md §2. Split that section into a shipped-ingestion
bullet and a still-missing-transmitter/revocation-wiring bullet, matching
the labelling pattern the chapter already uses elsewhere, and fixed the
chapter's own intro sentence, which claimed all of sections 1-3 were
pure roadmap targets and was now wrong twice over.

One thing I corrected after the workflow returned, having written the
methods myself: it described magic link and OTP as leaving has_enrolled
"at their defaults". Both actually override it to explicitly return
false — the same value, but a stated decision rather than an inherited
one, which their own doc comments say in as many words. Reworded so the
chapter reflects the deliberateness rather than implying an oversight.

Two things surfaced and deliberately left alone, per the same
don't-invent-scope instruction the chapter agents were given:

- CredentialStore::delete_credential's tightened atomicity contract
  (recovery codes' single-use guarantee depends on it) has nowhere to
  attach — neither ch02 nor ch03 shows CredentialStore's actual
  definition, only mentions it in passing, so adding the contract detail
  would mean writing a new section rather than fixing a stale one.
- ch02 has a pre-existing, unrelated inconsistency (its "Architectural
  Decisions" section describes DID/GNAP work its own callout two
  paragraphs above says doesn't exist) that predates this release and is
  out of scope for a v0.13.0 sweep.

Paired with the mechanical pass in the previous commit (RFC status
lines, roadmap.md Phase 2 checkboxes and crate-status corrections).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu

* fix(docs): restore two sentences my status-line edit had truncated

Review on #410 caught it: the mechanical status-line rewrite in
5051f10 replaced each RFC's leading blockquote wholesale with new
text, and my replacement text for RFC-011 and RFC-012 was itself
truncated — each ended mid-sentence ("this document is" / "makes it
different from") where the original continued.

Restored both continuations verbatim from the pre-edit commits
(59f3b35 for RFC-011, ed19e45 for RFC-012) rather than writing new
prose, since the point was to correct the status line, not rephrase
content that was already right.

RFC-010's blockquote was checked too, on the same theory the bug might
recur — it does not; that one's original text ended cleanly and my
edit preserved it intact.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
marcjazz added a commit that referenced this pull request Sep 26, 2026
* docs: mark Phase 2 shipped, correct RFC status lines, fix ssf/policy status

Mechanical pass ahead of a book-wide sweep for v0.13.0.

RFC-010/011/012 all said "proposed, not implemented" despite shipping
in v0.13.0 (#400, #402, #404) — the same class of staleness as the
README version pins fixed in #409, just in the status line instead of
an install snippet. Corrected to "implemented", each naming its feature
flag and module.

Phase 2's three checkboxes in roadmap.md were unchecked even though the
gate plus all three methods are merged; checked them and added a
one-line note on the two pieces of shared store infrastructure
(AtomicDecrement, the tightened CredentialStore contract) that came out
of building them, since a future feature is the likeliest reader of
that note.

`authkestra-ssf` was listed as "(planned)" in the crate-status section
and named again under Phase 4 as future work ("Implement SSF/CAEP for
real-time revocation") — but it shipped as #309, before this session's
work began. Relabelled "(shipped, receiver only)", and Phase 4 now
names precisely what the crate's own module doc says is still missing:
revoking or attenuating a live session on an event, transmitting SETs
of its own, and CAEP-aware middleware. `authkestra-policy`'s "proof of
concept, nothing calls it yet" status was checked against the source
and left unchanged — it is still true.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu

* docs: bring the mdBook up to v0.13.0

Fanned out over the book's 11 chapters (a Workflow run, one agent per
chapter plus a cross-chapter verify pass) after scouting which ones
plausibly needed anything: five had no v0.13.0-relevant content and are
untouched (ch01, ch04, ch05, ch06, ch07, ch10, ch11 — checked, not
skipped) or made no change after checking; four did.

ch08 (Getting Started Tutorial): the same `version = "0.7"` pin bug
already fixed in the top-level READMEs (#409), four occurrences, fixed
to 0.13 the same way.

ch02 (Core Engine and Identity): its KvStore/CredentialStore persistence
list was missing recovery-code credentials and the magic-link/OTP
one-time secrets — verified each against its actual store bound in
source before adding it.

ch03 (Core Traits): the chapter that documents AuthMethod and the
KvStore atomic-primitive family in depth, so it had the most real gaps.
Added the three new AuthMethod impls with their feature flags and RFC
references; added AtomicDecrement alongside AtomicConsume/AtomicInsert;
and corrected two "planned traits" bullets that flatly denied
authkestra-ssf and authkestra-policy exist, when both are real, published
crates (receiver-only and unwired-PoC respectively) — the chapter
predates both shipping.

ch09 (Security and Threat Model): added re-proof gating as a threat this
release closes — a hijacked-but-merely-logged-in session can no longer
enrol the attacker's own factor — since this chapter already enumerates
shipped security properties and re-proof gating is squarely one. The
cross-chapter verify pass then caught what no single-chapter read could:
this same chapter's CAEP section still said "no SSF/CAEP receiver...
in the workspace", flatly contradicting the ssf correction just made in
ch03 and docs/roadmap.md §2. Split that section into a shipped-ingestion
bullet and a still-missing-transmitter/revocation-wiring bullet, matching
the labelling pattern the chapter already uses elsewhere, and fixed the
chapter's own intro sentence, which claimed all of sections 1-3 were
pure roadmap targets and was now wrong twice over.

One thing I corrected after the workflow returned, having written the
methods myself: it described magic link and OTP as leaving has_enrolled
"at their defaults". Both actually override it to explicitly return
false — the same value, but a stated decision rather than an inherited
one, which their own doc comments say in as many words. Reworded so the
chapter reflects the deliberateness rather than implying an oversight.

Two things surfaced and deliberately left alone, per the same
don't-invent-scope instruction the chapter agents were given:

- CredentialStore::delete_credential's tightened atomicity contract
  (recovery codes' single-use guarantee depends on it) has nowhere to
  attach — neither ch02 nor ch03 shows CredentialStore's actual
  definition, only mentions it in passing, so adding the contract detail
  would mean writing a new section rather than fixing a stale one.
- ch02 has a pre-existing, unrelated inconsistency (its "Architectural
  Decisions" section describes DID/GNAP work its own callout two
  paragraphs above says doesn't exist) that predates this release and is
  out of scope for a v0.13.0 sweep.

Paired with the mechanical pass in the previous commit (RFC status
lines, roadmap.md Phase 2 checkboxes and crate-status corrections).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu

* feat(landing): add the three v0.13.0 passwordless methods

The "Eight ways to prove who someone is" grid had not moved since it
was written — three real AuthMethod implementations shipped in
v0.13.0 (magic link, email/SMS one-time codes, recovery codes) and
none of them were on the page a visitor actually lands on.

Inserted after TOTP, before the OAuth2/OIDC group, matching the order
docs.authkestra.com's sidebar now uses (fixed in the paired docs PR).
Heading updated to "Eleven ways". Recovery codes' card body says what
it is precisely — a look-up secret authenticator, not tied to TOTP —
since that distinction is the one thing worth getting right about that
method in one sentence.

Three new icons needed adding to the hand-rolled Lucide registry
(`link`, `message-square-code`, `list-checks`), fetched verbatim from
lucide-static 1.47.0 rather than approximated from memory, matching
this file's own stated sourcing practice ("extracted verbatim so no
runtime icon package ships").

Also fixed: Quickstart.astro's install snippet was still pinned to
`version = "0.9"`, the same class of staleness as the top-level READMEs
(#409) and the mdBook (#410) — five minors behind.

Verified with a real `astro build`: the output HTML has all eleven
`<h3>` cards in the intended order, and the heading text updated
correctly. A stale, gitignored `dist/` directory from an unrelated
prior build briefly looked like it meant something was wrong (it listed
routes like /providers/totp/) — it didn't; those come from
astro.config.mjs's legacy MOVED_TO_DOCS redirect list for URLs that
existed before docs.authkestra.com existed, which correctly needs no
entry for pages that never lived on this domain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu

* fix(landing): drop the nonexistent "session" feature from Quickstart

Review on #411 caught it: the facade's `session` feature doesn't exist —
it was folded into `authkestra-engine` unconditionally when sessions
became part of the engine (#375), and crates/authkestra/Cargo.toml says
so explicitly ("There is deliberately no `session` ... feature here").
Anyone copying this Cargo.toml line got
`error: none of the selected packages contains these features: session`
before ever reaching the Rust.

Verified against source rather than taking the suggestion on faith: the
sample uses `MemoryStore` and `Authkestra::builder()`, both of which
need the facade's `store` re-export and `core` alias — gated on
`memory` (which itself pulls in `engine`, so nothing else needs adding).
Traced the exact cfg gates in crates/authkestra/src/lib.rs to confirm
`["axum", "memory", "github"]` is both sufficient and minimal for this
sample, not just plausible.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu

* feat(landing): rebuild the comparison table around real strengths

Prompted by "have you updated the feature comparison board? should
that table even exist honestly?" The honest answer was no on both
counts: I hadn't looked at it since checking one unrelated version
pin, and once I actually checked the three names in it, one was a
real problem — better-auth.rs is a pre-1.0 alpha (1.0.0-alpha.2), and
its own docs say APIs may change without notice between alpha
releases. Comparing a stable v0.13.0 release against an undisclosed
alpha with five confident "yes" checkmarks was not an honest
comparison, independent of anything this release changed.

Replaced it with oauth2-passkey: real, shipped, not alpha-labelled,
and honestly narrower in scope (OAuth2 relying-party + WebAuthn only,
confirmed via its own README — no OP role, no TOTP, no resource
server). A fair comparison against a real project beats a favourable
one against an unstable strawman.

Rauthy and Kanidm both re-verified against their own docs rather than
carried over from whenever this was last written: Kanidm's native
OAuth2/OIDC provider and WebAuthn support confirmed on
kanidm.github.io, Rauthy's passkey-first design and brute-force/IP
rate-limiting confirmed on its GitHub README. Both check out.

Rewrote rows to show what actually differs, using only what v0.13.0
and existing crates can back up:

  - OAuth2/OIDC role: Authkestra plays all three (provider, client,
    resource server) from one crate. Neither alternative does — this
    is the genuine structural difference, not a marketing framing.
  - Resource server: named DPoP (RFC 9449) and mTLS (RFC 8705) binding
    specifically, both real (crates/authkestra-resource), rather than
    a bare "yes".
  - Passwordless methods: names the actual count (5) instead of a bare
    "Multi-factor auth: yes", which is what the whole session's work
    had made most stale.
  - New row, "Fresh proof to enrol a factor": RFC-009's re-proof gate
    is a genuine, checkable differentiator I could find no equivalent
    documented primitive for in either alternative. Marked "Not
    documented" for both rather than a confident "No" — the honest
    claim is an absence of evidence, not evidence of absence.
  - Bot protection: named Authkestra's actual three providers
    (Turnstile/hCaptcha/reCAPTCHA, crates/authkestra-engine/src/captcha.rs)
    against what Rauthy's docs actually describe (rate-limiting and
    IP-blocking — a different mechanism, not "not offered" and not a
    false equivalence to CAPTCHA either).

One thing caught in my own second pass, after finishing the rewrite:
the oauth2-passkey cell for "Configuration safety" first read
"Standard builder", carried over from the old better-auth.rs row
without verifying oauth2-passkey uses a builder at all. Fixed to "Not
documented" — the same discipline the rest of the rewrite was supposed
to hold everywhere, caught here on a re-read rather than in review.

Renamed the internal `betterAuth` variable to `embeddedAlt` throughout
so the code doesn't keep naming a competitor that's no longer in it.

Verified with a real `astro build`: all ten row labels render, the
table caption and both `.detail` write-ups describe the new middle
column, and no reference to "better-auth" remains anywhere in the
built output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XaoYHiT9Vb85XhBdVCg9Xu

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant