Command Palette
Search for a command to run...

Authentication & sessions

Login is passkey-style: there is no password sent to the server. A device proves it holds its private signing key by signing a fresh server challenge.

Challenge / response

Challenge

The client sends its auth public key. The server returns a fresh random challenge (single-use, short-lived, a couple of minutes) bound to that key.

Sign

The device signs the challenge with its auth private key (hybrid: ML-DSA-65 ‖ Ed25519).

Verify → token

The server verifies the signature and, if the device is active, issues a session token.

The challenge is single-use and expires quickly, so a captured challenge can't be replayed.

Session tokens

Tokens are PASETO v4.local, symmetric, encrypted (with authentication) under a server-only key kept in instance config:

  • Stateless. The server doesn't store sessions; it validates the token cryptographically on each request.
  • Short-lived. ~15-minute lifetime; the client transport re-authenticates transparently when one expires.
  • Audience-bound. The token carries this deployment's server URL as its audience, so a token leaked from one instance is rejected by another.
  • Carries the user id, the device id, issue/expiry times, and a claim version that must match (so a token shape change cleanly invalidates old tokens).

SSO (IDP mode)

A deployment in IDP mode replaces the password onboarding above with OpenID Connect:

Auth-code + PKCE

The client runs a standard OIDC auth-code flow with PKCE (a public client, no client secret) against your IdP and obtains an ID token. Attribute providers also receive an IdP-API access token, used only to reach the IdP's own attribute store, never sent to open-secret.

Exchange for a session

The client calls IdpService.ExchangeSSOToken with the ID token. The server validates it (issuer / audience / expiry / signature against the IdP's JWKS), JIT-provisions the user + a single identity device on first login, and mints the same PASETO session token the password flow issues.

Derive the vault key

The client joins its non-server factor (factor1) with the server-held K2 to unlock the escrowed identity keypair. The server never sees factor1. See Key custody.

Challenge/Verify still exist in IDP mode (the identity device has a real auth keypair, used for token refresh); only password Signup is gated off.

Public vs. authenticated endpoints

Only a handful of operations are reachable without a valid token - everything else requires one:

Public (no token)Why
AuthService.SignupCreate the first credential (standalone mode).
AuthService.ChallengeBegin a login.
AuthService.VerifyComplete a login, get a token.
InstanceService.GetConfigRead non-secret instance config (signups open, backup enabled, auth mode, and the public OIDC client config in IDP mode).
IdpService.ExchangeSSOTokenExchange a validated OIDC ID token for a session (IDP mode).

GetConfig surfaces the deployment's auth_mode and, in IDP mode, the public OIDC client config (issuer, client id, provider kind, scopes) so the frontend can start PKCE. It never surfaces a client secret (there is none) or the K2 unlock half. IdpService.GetWrappingFactorK2 and PutEscrowedVault are session-gated, not public.

Every User, Device, and Entry RPC is authenticated, and identity comes from the token, not from the request body, so you can only ever read or change rows you own or that another user has explicitly shared with you.

Device revocation

Revoking a device (see Devices & sharing) takes effect immediately, on three fronts:

  • New logins from the revoked device are refused at Challenge/Verify, it can never obtain a fresh token.
  • Signed commands (delete, rename, share, …) re-verify device trust, so a revoked device can't issue them even while holding a still-valid token.
  • Plain reads (Get/List), even though tokens are stateless, the auth interceptor runs a per-request device-active check, so a revoked device's existing token is rejected on its very next request rather than lingering until the token expires.