Skip to content

Campus passkey authentication

Campus accounts are created and accessed with discoverable WebAuthn passkeys. Registration is open to everyone and does not depend on an invitation or account fee. A person chooses one globally unique Campus name. Campus assigns that name in the same database transaction that creates the account, credential, and default installations; if another account wins the name first, the transaction fails with a conflict. There is no separate display name or post-registration naming flow.

Internally, every account still has an opaque acct-<uuid> key. That value is the discoverable credential's WebAuthn user handle and remains the stable owner key for sessions, installations, files, and authorization. It is not returned as the public account ID or rendered in the interface.

Trust boundary

The browser receives one-time WebAuthn options from CampusOS and sends back an explicit server-safe credential payload. CampusOS uses WebAuthn4J to verify:

  • challenge value, operation, expiry, and one-time use;
  • the exact configured browser origin and relying-party ID;
  • credential ID consistency and the attested public key;
  • assertion signatures and signature counters;
  • user presence and required user verification;
  • the discoverable credential user handle;
  • backup eligibility and backup state.

Campus stores public credential material only. Requests containing PRF output, private keys, seeds, mnemonics, or plaintext account bundles are rejected.

Local configuration

The development defaults are:

text
origin:      http://localhost:8090
RP ID:       localhost
RP name:     RunCampus
session:     30 days
challenge:   5 minutes
cookie:      campus_session

Open the browser at http://localhost:8090. Although Jetty binds to 127.0.0.1, registering at the IP address creates a different browser origin and will fail the configured WebAuthn verification.

Configuration variables:

text
CAMPUS_OS_ORIGIN
CAMPUS_RP_ID
CAMPUS_RP_NAME
CAMPUS_CHALLENGE_TTL_MS
CAMPUS_SESSION_TTL_MS
CAMPUS_SESSION_COOKIE

Production HTTPS automatically adds the Secure cookie attribute. Origin and RP changes require a process restart and must be treated as a credential migration.

API flow

Registration:

text
POST /v1/auth/register/options  { "name": "roman-tsopin" }
  → navigator.credentials.create(...)
POST /v1/auth/register/verify
  → account + HttpOnly session cookie

Login:

text
POST /v1/auth/login/options
  → navigator.credentials.get(...) using a discoverable passkey and an RP-scoped PRF input
POST /v1/auth/login/verify
  → account + HttpOnly session cookie

The same login assertion can supply a PRF result to the trusted browser shell. CampusOS verifies the assertion after the shell removes the PRF result from the submitted payload; it never receives that result. The shell may use it to migrate an older encrypted app-data recipient, then retain only that non-financial account recipient in account-bound browser storage. Contacts, Drive, Drop, and other authorized interactive clients can therefore survive shell reloads and new tabs without another passkey ceremony. Logout drops in-memory handles but does not erase the browser's matching account recipient. Wallet recovery, signing authority, and the raw PRF result are never written there.

An account created before this boundary may still have an app-data recipient sealed only to its login passkey. Its next normal login migrates that recipient into session access. Apps report this one-time migration requirement explicitly; they never open a hidden WebAuthn prompt, silently replace the recipient, or strand existing encrypted data.

Older user-secret bindings used a credential-specific salt that could not be requested during a username-free login. Their next explicit unlock evaluates the old and RP-scoped salts together, rewraps the bundle once in the browser, and thereafter uses the normal login ceremony.

Session management:

text
GET  /v1/auth/session
POST /v1/auth/logout

The browser never submits the opaque account key to app lifecycle endpoints. CampusOS derives it from the active session for catalog, install, start, stop, and descriptor operations. Public app routes accept the readable Campus name.

Isolated application origins

The CampusOS cookie is host-only. An application such as campus-files.localhost must never receive it. CampusOS opens an installed app with its per-app Iroh endpoint ticket and random capability in a URL fragment. The app can move that value to its own origin-scoped session storage and clear the fragment.

Future Bitcoin integration

Account identity, payment authorization, compute leases, and vendor entitlements are distinct concepts. The passkey account is the stable owner to which a future wallet or payment app can attach. Private Bitcoin keys should remain client-held. A wallet may use its own encrypted bundle/PRF binding and fresh passkey assertions for high-risk actions without putting private payment material in the CampusOS session.

Verification

test/campus/webauthn_test.clj creates real P-256 keys and signed WebAuthn registration/assertion payloads. It covers open registration, username-free login, hashed sessions, origin rejection, counters, and one-time challenges.

bb smoke additionally performs a synthetic but fully verified registration, logout, discoverable login, authenticated installation, Docker lifecycle, and CampusSDK/Iroh request path. It contains no authentication bypass.

Software belongs to people. Campus gives it a durable place to run.