ADR 0001: Issue and validate JWTs with standard .NET libraries, not Noundry.Authnz
- Status: Accepted, 2026-08-28
Context
Section titled “Context”Noundry.Authnz was considered for Bookend’s authentication. Version 1.3.0 is an OAuth 2.0 / OIDC client library: sign-in with Google, Microsoft, GitHub and similar providers, cookie-based sessions and Razor tag helpers. It has no surface for issuing RS256 tokens, publishing JWKS, rotating refresh tokens, email sign-in links or API-key exchange, all of which Bookend requires, and its cookie and session model conflicts with Bookend’s stateless bearer-token design.
Decision
Section titled “Decision”Bookend implements its own identity issuer as application services over the users, auth_refresh_tokens, auth_otp_tokens, api_keys and auth_events tables, using:
Microsoft.IdentityModel.JsonWebTokensto sign RS256 access tokens (15 minutes by default) with a keypair generated on first boot and stored encrypted insettings(auth.signing_key);Microsoft.AspNetCore.Authentication.JwtBearerto validate them, with the public key published at/v1/.well-known/jwks.json;- Argon2id for password hashing, and SHA-256 for refresh-token, sign-in link and API-key material at rest;
- rotating refresh tokens, revocation, emailed single-use sign-in links (Noundry.Sanquhar over the bank’s SMTP relay) and API-key to JWT exchange at
/v1/auth/token.
Noundry.Authnz is not referenced anywhere in the solution.
Consequences
Section titled “Consequences”- Bookend owns a small but security-critical component. It is covered by an authorization-matrix contract test (every endpoint against every role) and by end-to-end sign-in tests as release gates.
- Tokens live in memory on the client; there are no cookies and no server sessions, which is also what lets the MCP surface share the same bearer authentication.
- Later additions built on the same issuer: authenticator-app MFA (RFC 6238 TOTP on standard .NET cryptography) and OpenID Connect federation, where the api acts as a confidential client, validates the IdP’s id_token and then issues the same token pair as password sign-in. Authorization, refresh rotation and audit are identical for every sign-in method.
- If Noundry later ships a token-issuing package, adopting it would be a new ADR; nothing here forecloses it.