Overview
Authentication in Snapp is host-aware, organization-scoped, and server-enforced.
It is built on top of Better-Auth, extended to support:
- multi-host deployments
- per-organization identity
- dynamic roles and permissions
- email-based workflows
- optional OAuth providers
- API keys
- enforced two-factor authentication (2FA)
There is no client-side trust.
All authentication and authorization decisions are resolved on the server using the active host and organization.
Authentication boundaries
Authentication is scoped by three dimensions:
Host
Derived from the request origin and matched againstsettings.yaml.Organization
Determined by the host and mapped to a slug used as the canonical identifier.Session
Cookie-based, validated on every request.
A session is never valid outside its host and organization context.
Host-aware auth instances
Snapp maintains a dedicated authentication instance per host.
- Each host creates its own Better-Auth client
- Instances are cached in memory
- Cache is invalidated when:
- roles are updated
- teams are modified
- organizations change
- host configuration changes
This provides isolation without duplicating infrastructure.
Supported authentication methods
Username and password
- Primary authentication method
- Passwords are hashed
- Email verification is mandatory
- Auto sign-in is disabled
Signup can be disabled per host:
disable:
signup: true
Email verification
- Required before account activation
- Verification emails are sent on:
- signup
- sign-in if the email is still unverified
If SMTP is disabled, verification emails are logged to stdout.
Password recovery
- Token-based reset flow
- Tokens are single-use
- Reset links are bound to the originating host
- Delivery follows SMTP configuration
Two-Factor Authentication (2FA)
Snapp enforces TOTP-based two-factor authentication by default.
Enforcement model
- Users without 2FA are redirected to setup
- Access to protected routes is blocked until setup completes
- Enforcement can be disabled per host:
disable:
twoFactor: true
2FA setup flow
During setup, the user is presented with:
- A TOTP URI
- A QR code generated server-side
- A set of one-time backup codes
The setup modal allows the user to:
- Scan the QR code with an authenticator app
- Download the QR code as an SVG
- Download the raw TOTP URI as a text file
- Download backup codes as a text file
All artifacts are generated client-side from server-issued data. The secret itself is never stored or transmitted again after setup.
Backup codes
- Generated once during setup
- Displayed a single time
- Must be explicitly downloaded by the user
- Can be used if the authenticator device is lost
Snapp does not regenerate backup codes automatically.
OTP verification
After setup:
- Users must confirm a valid OTP
- OTP validation is enforced server-side
- Failed attempts are rejected without revealing state
API keys
API keys are first-class authentication credentials.
- Generated per user
- Optional expiration
- Rate-limited
- Scoped to the organization
Rate limits are derived from host limits:
requestsPerDayrequestsPerMinute
API keys are subject to the same enforcement rules as authenticated sessions unless limits are disabled.
OAuth authentication
Snapp supports optional OAuth providers.
Supported providers
- GitHub
OAuth is enabled only if provider credentials are present in the environment.
Provider configuration is loaded from:
config/oauth.json
OAuth behavior
- OAuth accounts are linked to users
- If a matching account exists, it is reused
- Otherwise, a new user is created
- Email verification rules still apply
OAuth does not bypass:
- organization membership
- permission checks
- 2FA enforcement
- watchlist validation
Organizations
Organizations are first-class authentication entities.
Key properties:
- Users can belong to multiple organizations
- One organization is always active
- Organization ID is derived from the host origin
- Cross-organization access is forbidden
On each request:
- The active organization is validated
- If missing, it is automatically set
- If invalid, access is denied or redirected
Teams
Teams exist inside organizations and are used for scoped permissions.
They enable:
- granular access control
- shared ownership of resources
- permission delegation without role inflation
Roles and permissions
Snapp uses an explicit access control model.
Roles
Default roles include:
owneradminmember
Roles define permissions declaratively, not through implicit logic.
Permissions
Permissions are expressed as actions on resources:
create
read
update
delete
cancel
Example:
urls: ['create', 'read', 'update', 'delete']
Permissions are:
- evaluated server-side
- scoped to organization and team
- dynamically reloadable
Dynamic permission model
Permissions are stored in the database and merged with defaults at runtime.
Effects:
- No redeploy required
- Changes propagate immediately
- Auth caches are invalidated automatically
Authorization enforcement
Every protected operation performs explicit permission checks.
Examples:
- URL creation →
urls.create - URL update →
urls.update - URL deletion →
urls.delete - Team creation →
team.create - Role updates →
team.update
UI state never implies authorization.
Invitations
Snapp supports controlled onboarding through invitations.
Organization invitations
- Invite users to an organization
- Assign roles
- Optional expiration
- Host-bound acceptance flow
Admin user invitations
- Create users without public signup
- Force password setup via email
- Used in restricted environments
Session handling
- Cookie-based sessions
- Short-lived session cache (5 minutes)
- Automatic refresh
- Invalid sessions are rejected
Users are redirected based on state:
- unauthenticated → sign-in
- missing organization → invitation flow
- missing 2FA → setup flow
Watchlist enforcement
Authentication is subject to watchlist rules.
Checks include:
- username
- domain
A failed check results in authentication rejection with no state mutation.
Email delivery model
Authentication-related emails are:
- rendered server-side
- sent via SMTP if enabled
- logged to stdout if SMTP is disabled
Supported emails:
- verification
- password reset
- OTP
- organization invitation
- admin invitation
Guarantees
- Authentication is host-isolated
- Authorization is server-enforced
- Secrets are never exposed twice
- 2FA cannot be bypassed client-side
- Configuration changes apply live
Notes
- Authentication behavior is driven by
settings.yaml - OAuth is optional
- SMTP is optional
- Beta releases may extend providers and permission primitives