6.2 KiB
Auth architecture overview
This document consolidates how bitvid authentication works today, with a focus on
js/services/authService.js and the provider implementations in
js/services/authProviders/. It complements the storage details in
docs/nostr-auth.md and the remote signer specifics in
docs/nip46-remote-signers.md.
Core building blocks
- AuthService (
js/services/authService.js) orchestrates login/logout flows, maintains the active identity, updates saved profile storage, and emits auth events for the rest of the app. - Auth providers (
js/services/authProviders/) are pluggable adapters that implementlogin()for a specific signing strategy:nip07(browser extension signer)nsec(direct/private key signer stored locally)nip46(remote signer via Nostr Connect)
- State + storage lives in
js/state/appState.jsandjs/state/cache.js, backed bylocalStoragekeys described indocs/nostr-auth.md.
AuthService responsibilities
Provider selection and normalization
AuthService.requestLogin() is the entry point for logins. It:
- Normalizes the requested provider id (defaults to
nip07). - Calls the provider’s
login()method with the activenostrClientand provider-specific options. - Normalizes the provider result into
{ pubkey, authType, signer }. - For NIP-07, requests extension permissions before continuing.
- Either returns the normalized result (when
autoApply: false) or callslogin()to apply the identity.
Applying a login
AuthService.login(pubkey, options) is responsible for applying the session:
- Identity + session state
- Normalizes the pubkey to hex, writes it to
appState(setPubkey) and stores the bech32npub(setCurrentUserNpub). - Updates
nostrClient.pubkeyso downstream services use the new identity.
- Normalizes the pubkey to hex, writes it to
- Saved profile persistence
- Reads + updates the saved profile list in
js/state/cache.js. - Persists
bitvid:savedProfiles:v1with the latestauthType+providerId. - Ensures there is an
activePubkeyentry whenpersistActiveis true.
- Reads + updates the saved profile list in
- Post-login hydration
- Kicks off async profile/block/relay loading via
applyPostLoginState()and emitsauth:post-loginfor consumers (UI updates, etc.).
- Kicks off async profile/block/relay loading via
Logout and profile switching
logout()clears the active pubkey/npub, resets moderation + caches, and keeps the saved profile list intact so users can quickly re-auth.switchProfile()replays the login flow for a previously saved pubkey and moves it to the front of the saved list.
Provider flows
NIP-07 (browser extension signer)
- Implemented by
js/services/authProviders/nip07.js. - Delegates
login()tonostrClient.login()and expects a pubkey (and optional signer) in the result. - After provider login,
AuthServicerequests extension permissions before completing the session. Permission failure aborts the login.
Key behavior: signing stays inside the browser extension; bitvid only stores
pubkey metadata in localStorage.
nsec (local signer)
- Implemented by
js/services/authProviders/nsec.js. - Supports two modes:
- Unlock stored key (
unlockStored: true): usesnostrClient.unlockStoredSessionActor()and requires a passphrase. - Direct secret: derives a key from an
nsec, hex key, or seed vianostrClient.derivePrivateKeyFromSecret()and registers a local signer withnostrClient.registerPrivateKeySigner().
- Unlock stored key (
- Can optionally persist the encrypted key on the device; authService still only stores public metadata in the saved profile list.
Key behavior: signing happens locally in the browser with a derived private key; storage of the private key is optional and encrypted when persisted.
NIP-46 (remote signer / Nostr Connect)
- Implemented by
js/services/authProviders/nip46.js. - Supports three paths:
- Reuse stored session (
reuseStored: true): usenostrClient.useStoredRemoteSigner()to resume the saved NIP-46 session. - Manual connection (
mode: "manual"): use a providedconnectionString(e.g.,nostrconnect://orbunker://). - Handshake flow (
mode: "handshake", default): generate a handshake vianostrClient.prepareRemoteSignerHandshake()and then connect using the returned connection string + client keypair.
- Reuse stored session (
Key behavior: signing happens on a remote device. Session material is stored
in bitvid:nip46:session:v1 as described in
docs/nip46-remote-signers.md.
Session state + persistence
Auth state is intentionally small and split across caches:
- Active identity:
js/state/appState.jskeeps the currentpubkey+npubin memory and updatesnostrClient.pubkeyon login. - Saved profiles:
js/state/cache.jsmanagesbitvid:savedProfiles:v1, includingauthType+providerIdfor each entry. Seedocs/nostr-auth.mdfor the schema and migration notes. - Profile cache:
bitvid:profileCache:v1is used to show display names + avatars quickly; it is updated duringloadOwnProfile()calls. - Remote signer session:
bitvid:nip46:session:v1stores handshake/session data for NIP-46 reconnects. Seedocs/nip46-remote-signers.mdfor details.
NIP-04 / NIP-44 considerations
AuthService and the provider adapters do not implement NIP-04 or NIP-44
message encryption/decryption directly. They simply supply a pubkey and (when
available) a signer object back to the caller. When other features (like direct
messages) need NIP-04 or NIP-44 capabilities, they rely on the active signer
implementation on the nostrClient. Remote signers and extensions must support
those encryption methods for DM features to work, while local nsec signers can
provide the primitives directly.
Signing/encryption requests always flow through the signer adapter registry in
js/nostr/client.js (accessed via js/nostrClientFacade.js). The registry
selects the active signer, falls back to the strongest supported capability, and
triggers permission prompts (for example, the NIP-07 extension handshake) before
attempting privileged calls.