Skip to content
Fermium

Sign-in

Google for people, OAuth 2.1 for agents, hashed tokens and passwords, and how a gated document checks who is reading.

There are three ways to prove identity to Fermium, one for each kind of caller.

People: Google sign-in

The web app and the OAuth consent flow use Google OpenID Connect. Fermium receives your email, Google account id, name and avatar; it never sees your Google password. A verified email is required. Any Google account works, consumer or Workspace.

A successful sign-in sets a session cookie on fermium.app: host-only, __Host- prefixed, HttpOnly, Secure, SameSite=Lax, valid for 90 days. Sign-in starts are rate limited per IP.

People: your own identity provider

On Business and above, a workspace can connect an OpenID Connect provider (Microsoft Entra ID, Okta, or any compliant one) for the email domains it owns. The flow is authorization code with PKCE and a nonce; Fermium discovers the provider's endpoints from its issuer, verifies the ID token's signature against the provider's published keys and checks issuer, audience and nonce, then requires the email's domain to be one the workspace configured. The provider's client secret is stored encrypted and never returned. In enforced mode, a Google sign-in for one of those domains is redirected to the provider, so the provider's controls are the only way in. The resulting session cookie is the same as above.

On Enterprise, the provider can also provision and deactivate accounts and push groups over SCIM 2.0 with a bearer token that is stored hashed. Setup and behaviour are in SSO and SCIM.

Agents: OAuth 2.1

MCP clients do not get your Google credentials and do not get a pasted token. They run a standard OAuth 2.1 flow against fermium.app:

  1. The client calls /mcp without a token and gets a 401 with a WWW-Authenticate header pointing at the protected-resource metadata.
  2. It reads the authorization server metadata, registers itself (dynamic client registration, public client, no secret), and opens the browser to /oauth/authorize with a PKCE challenge.
  3. You sign in with Google if needed and approve the client once. Later authorizations for the same client complete silently.
  4. The client exchanges the code for an access token (7 days) and a refresh token (365 days from the grant, rotated on every use).

Access tokens are ordinary dsk_ tokens stored as SHA-256 hashes. Revoking one in the app also revokes its grant, so the client cannot silently refresh a replacement. Endpoints and parameters are in Authentication.

Scripts: static tokens

A dsk_ token minted in Settings, then API keys and tokens works the same way as an OAuth access token but with a lifetime you choose (default 365 days; 0 for never). Only the hash is stored; the plaintext is shown once. Lookup is by prefix with a constant-time hash comparison, so a database leak yields no usable tokens.

Readers of gated documents

Password documents use HTTP Basic with a per-document realm, so the browser remembers each document's password separately. Passwords are stored as argon2id hashes. Failed attempts are throttled per IP and per document.

Restricted documents need proof that the reader signed in with an account that has permission to the document. The content host cannot see the app's session cookie, so:

  1. The content host redirects to https://fermium.app/viewer/authorize?slug=....
  2. The app host checks the session (starting Google sign-in if there is none) and checks that the account has permission to the document. If not, it answers 403; a workspace member can request access from that page.
  3. It mints a one-time code (60-second lifetime) and redirects back to the content host, or to the custom domain the reader came from if that domain belongs to the same workspace.
  4. The content host redeems the code, sets a viewer cookie valid for 12 hours, and redirects to the clean document URL.

The viewer cookie carries only the email, and the content host rechecks permission on every request, so revoking access takes effect at once.

What is hashed and what is not

Secret At rest
Document passwords argon2id hash
Access and static tokens SHA-256 hash; plaintext shown once
Refresh tokens SHA-256 hash; rotated on use
Session and viewer cookies signed with the server secret, not stored server-side
Upload tickets signed URLs with a 60-minute lifetime
Identity provider client secret (SSO) encrypted at rest with a key derived from a server secret; never returned
SCIM tokens, webhook secrets SCIM tokens as SHA-256 hashes, shown once; webhook secrets stored for signing, shown once
One-click keep links, invite links signed or random single-purpose tokens bound to one document or one address

Document slugs are not secrets in this sense; they are random 128-bit identifiers stored in the clear, because the link itself is the capability.