Authentication

DocPlatform supports local authentication (email + password) out of the box, with optional Google/GitHub OIDC sign-in and WebAuthn/Passkeys for passwordless login.

Local authentication (default)

Local auth works without any configuration. Users register with an email and password, and sign in with the same credentials.

How it works

  1. Registration — User submits email + password. Password is hashed with argon2id (OWASP 2024 recommended algorithm).
  2. Login — User submits credentials. Server verifies the password hash and returns JWT tokens.
  3. Session — Access token (RS256, 15-minute lifetime) is sent with each API request. Refresh token (7-day lifetime, rotated on each use) is used to obtain new access tokens without re-authentication.

Password hashing

DocPlatform uses argon2id with the following parameters (OWASP 2024 standard):

Parameter Value
Algorithm argon2id
Memory 64 MB
Iterations 3
Parallelism 2
Salt length 16 bytes
Key length 32 bytes

The memory, iterations, and parallelism parameters are configurable via ARGON2_MEMORY, ARGON2_TIME, and ARGON2_THREADS environment variables. See Environment Variables. Password hashes are stored in the SQLite database and never leave the server.

Password reset

When a user requests a password reset:

  • With email configured (SMTP or Resend) — a one-time reset link is emailed to the user
  • Without email — nothing is sent, and the link is not written to the server logs (the server only notes that an email was skipped). An admin generates a reset link on the server instead:
docplatform reset-password --email user@example.com

The token expires after 1 hour and can only be used once.

Invite-only mode (disabling public signup)

By default, anyone who can reach your instance can create an account at /register. On a private or internal deployment you usually want the opposite: only people you invite should be able to join.

Set DOCPLATFORM_DISABLE_SIGNUP=true and restart the server:

export DOCPLATFORM_DISABLE_SIGNUP=true

With invite-only mode on:

  • Public registration is blockedPOST /api/auth/register returns 403 SIGNUP_DISABLED, and the login page hides the “Create one” link and the register form.
  • First-time OIDC sign-in for an unknown user is blocked — a Google/GitHub login that would otherwise create a brand-new account is rejected the same way. Users who already have an account (including existing OIDC users) sign in normally.
  • Invitations still work — a user who accepts a workspace invitation still gets an account created for them. This is how you onboard people in invite-only mode: invite them from Workspace Settings → Members, and they set their name and password when they accept.
  • Share links still work — an already-signed-in user can join a workspace via a share link.

To onboard the very first user before turning this on, either register that account first and then set the flag, or leave the flag off, register, and turn it on afterward. The setting is read at startup, so changing it requires a restart.

JWT tokens

DocPlatform issues RS256 (RSA-SHA256) JSON Web Tokens for authentication.

Token lifecycle

User logs in
    │
    ▼
┌─────────────────────────────────┐
│ Access Token (15 min)            │  ──►  Sent with every API request
│ Refresh Token (7 days)           │  ──►  Used to get new access tokens
└─────────────────────────────────┘
    │
    │  Access token expires
    ▼
┌─────────────────────────────────┐
│ POST /api/auth/refresh            │
│ Body: { refresh_token: "..." }   │
└─────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────┐
│ New Access Token (15 min)        │  ──►  Old refresh token rotated
│ New Refresh Token (7 days)       │  ──►  Old one invalidated
└─────────────────────────────────┘

Refresh token rotation

Each time a refresh token is used, a new refresh token is issued and the old one is invalidated. This limits the window of exposure if a token is compromised.

Configuration

Variable Default Description
JWT_KEY_PATH {DATA_DIR}/jwt-private.pem Path to the RS256 private key

Token lifetimes are fixed at 15 minutes (access) and 7 days (refresh) and are not configurable.

Key management

The RS256 key pair is auto-generated on first startup if the file doesn’t exist. To rotate keys:

  1. Stop the server
  2. Delete the key file ({DATA_DIR}/jwt-private.pem)
  3. Start the server — a new key is generated

All existing sessions are invalidated on key rotation. Users must log in again.

Google OIDC sign-in (optional)

Allow users to sign in with their Google account.

Setup

  1. Go to the Google Cloud Console
  2. Create a new project (or use an existing one)
  3. Navigate to APIs & ServicesCredentials
  4. Click Create CredentialsOAuth 2.0 Client ID
  5. Application type: Web application
  6. Add authorized redirect URI: https://your-domain.com/api/auth/oidc/google/callback
  7. Copy the Client ID and Client Secret

Set the environment variables:

export OIDC_GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
export OIDC_GOOGLE_CLIENT_SECRET=your-client-secret

Restart the server. A Sign in with Google button appears on the login page.

User provisioning

When a user signs in via Google for the first time:

  • A DocPlatform account is created with their Google email, along with their own organization (same as local registration)
  • Workspace access comes from invitations — the inviter picks the role
  • No password is set (they can add one later from their profile)

GitHub OIDC sign-in (optional)

Allow users to sign in with their GitHub account.

Setup

  1. Go to GitHub Developer Settings
  2. Click New OAuth App
  3. Set the authorization callback URL: https://your-domain.com/api/auth/oidc/github/callback
  4. Copy the Client ID and generate a Client Secret

Set the environment variables:

export OIDC_GITHUB_CLIENT_ID=your-client-id
export OIDC_GITHUB_CLIENT_SECRET=your-client-secret

Restart the server. A Sign in with GitHub button appears on the login page.

User provisioning

Same as Google — a DocPlatform account is created using the GitHub primary email, which DocPlatform fetches via the user:email OAuth scope (it works even when the email is not public on the GitHub profile).

WebAuthn / Passkeys (optional)

Allow users to sign in with hardware security keys, biometrics, or platform authenticators (Touch ID, Face ID, Windows Hello). This provides passwordless, phishing-resistant authentication.

Setup

Set the following environment variables:

export WEBAUTHN_RP_ID=docs.example.com
export WEBAUTHN_RP_DISPLAY_NAME="My Docs"
export WEBAUTHN_RP_ORIGINS=https://docs.example.com

Restart the server. A Sign in with Passkey button appears on the login page.

How it works

  1. Registration — User goes to Profile → Security → Register Passkey
  2. Browser prompts for biometric or security key verification
  3. A credential is stored on the server (public key only — the private key never leaves the device)
  4. Login — User clicks “Sign in with Passkey” and authenticates with their device

Managing credentials

Users can manage their passkeys from their profile:

  • List credentialsGET /api/auth/webauthn/credentials
  • Delete a credentialDELETE /api/auth/webauthn/credentials/:id

Each credential stores a name, last-used timestamp, and sign count for clone detection.

Requirements

Requirement Details
HTTPS Required — WebAuthn only works over secure connections
Browser support Chrome 67+, Firefox 60+, Safari 14+, Edge 79+
RP ID Must match the domain users access (no wildcards)

API keys

For programmatic access (CI/CD pipelines, MCP integrations, scripts), use API keys instead of JWT tokens.

How it works

  1. Create an API key from the web UI or API: POST /api/v1/api-keys
  2. The key is returned once in full (prefixed with dp_live_) — store it securely
  3. Use the key as a Bearer token: Authorization: Bearer dp_live_...
  4. Only the key hash is stored server-side (HMAC with configurable pepper)

Key management

Operation Endpoint
Create key POST /api/v1/api-keys
List keys (prefix only) GET /api/v1/api-keys
Delete key DELETE /api/v1/api-keys/:id
Rotate key POST /api/v1/api-keys/:id/rotate

Scopes

Each key carries a set of scopes checked on every request:

Scope Grants
read GET/HEAD/OPTIONS requests
write POST/PUT/PATCH (implies read)
delete DELETE (implies read)
admin All methods
admin:read Read-only platform-owner access

New keys default to ["read", "write", "delete"]. An optional expires_at (Unix seconds) can be set at creation. API-key requests must also pass the caller’s workspace role check — a scope never grants more than the underlying role allows.

Set API_KEY_PEPPER (or DOCPLATFORM_API_KEY_PEPPER) for HMAC hashing security.

Session management

DocPlatform tracks active sessions per user:

Field Description
Device User-agent string
IP address Client IP (for audit purposes)
Created When the session was established
Last active Most recent API request

Users can view their active sessions from their profile page (GET /api/auth/sessions).

Revoking sessions

  • Logout — revokes all of the user’s sessions, on every device (the session records are deleted, so even unexpired access tokens stop working)
  • Password reset — all of the user’s sessions are revoked automatically
  • Refresh-token replay — if a rotated refresh token is ever reused, all of that user’s sessions are revoked as a defensive measure
  • Key rotation — deleting the JWT key invalidates all sessions globally

Password policy

Constraint Value
Minimum length 8 characters
Maximum length 1024 characters
Hashing argon2id (64 MB memory, 3 iterations, parallelism 2)
Account lockout 5 failed login attempts → 15-minute lockout (tunable)

Passwords are validated on registration and password reset. DocPlatform does not enforce character-class requirements (uppercase, special characters) — length is the primary security measure per current NIST guidelines.

Brute-force lockout

After a number of consecutive failed password attempts, an account is temporarily locked to blunt online password guessing (including distributed credential-stuffing, which a per-IP rate limit alone does not stop). While locked, even the correct password is refused, and a successful login resets the counter. A locked account returns the same generic 401 as a wrong password — the lock is never revealed to an unauthenticated caller, so it cannot be used to enumerate which emails are registered. The lock applies to the password login and invitation-accept flows; OIDC and passkey sign-in are unaffected.

The policy is tunable via environment variables (see Environment Variables):

Variable Default Meaning
DOCPLATFORM_LOGIN_LOCK_THRESHOLD 5 Consecutive failures before lock (minimum 1)
DOCPLATFORM_LOGIN_LOCK_DURATION_SEC 900 Lock duration in seconds (minimum 1; default 15 minutes)

WebSocket authentication

WebSocket connections use an HttpOnly cookie mechanism to avoid exposing tokens in URLs (which would appear in server logs and browser history).

Flow:

  1. Client calls POST /api/auth/ws-token with a valid JWT
  2. Server sets a dp_ws_token HttpOnly, SameSite=Strict cookie (valid for 1 hour)
  3. Client connects to ws://host/ws — the browser sends the cookie automatically
  4. Server validates the cookie and establishes the WebSocket

This is transparent to users — the web editor handles token acquisition automatically.

Security recommendations

  • Enable OIDC for teams with Google or GitHub accounts — delegates password management to established providers
  • Use HTTPS in production — JWT tokens are bearer tokens; intercepted tokens grant full access
  • Keep token lifetimes short — 15-minute access tokens limit exposure
  • Monitor sessions — review active sessions periodically for unexpected devices or IPs
  • Rotate keys annually or after any suspected compromise
  • Use HttpOnly cookies — DocPlatform stores tokens in HttpOnly + Secure + SameSite=Strict cookies, preventing XSS token theft