-
-
Notifications
You must be signed in to change notification settings - Fork 2
auth flow
This page documents the end-to-end authentication flows for api.nself.org powered by the auth plugin (D2-T01). Implementation is in web/backend/plugins/auth/ and web/backend/migrations/025_np_auth_plugin_schema.sql.
For plugin install instructions see plugin-auth.md.
Five Postgres tables back the auth system (migration 025_np_auth_plugin_schema.sql):
| Table | Purpose |
|---|---|
np_users |
Primary identity records (email, hashed password, email_verified, source_account_id, tenant_id) |
np_sessions |
Server-side session store with revocation support |
np_mfa_devices |
TOTP enrollments and backup codes |
np_password_reset_tokens |
PKCE-style single-use reset tokens (15-min TTL) |
np_oauth_states |
CSRF state params for social login (10-min TTL) |
Multi-Tenant Convention Wall (Decision #20):
-
source_account_id TEXT NOT NULL DEFAULT 'primary'— isolates self-hosted app instances -
tenant_id UUIDnullable — Cloud customer isolation only - Never swap these. Confusing them causes cross-customer data leaks.
Status: Implemented in D2-T02 (stub: email send stubbed until D4/Postmark is live)
POST /auth/signup { email, password }
→ validate email format + password strength
→ check duplicate email (409 if exists)
→ rate-limit: 3 signups per email per hour
→ captcha verification (hCaptcha / Cloudflare Turnstile)
→ Argon2id hash password (time=3, mem=64MB, p=4)
→ INSERT np_users (email_verified = FALSE)
→ generate email verification JWT (15-min TTL)
→ send verification email via D4/Postmark (template: signup-verify)
← 201 { message: "Check your email to verify your account" }
Security invariants:
- Password never stored in plaintext or logged
- Duplicate email returns 409 with "email already registered" (no timing oracle)
- Unverified users cannot sign in (403 "check your email")
Status: Implemented in D2-T02
GET /auth/verify-email?token=<signed JWT>
→ verify JWT signature + TTL (15 min)
→ verify token is single-use (used_at check)
→ UPDATE np_users SET email_verified = TRUE
→ mark token used
← 200 redirect to /account
Re-send:
POST /auth/resend-verify { email }
→ rate-limit: 1 per 60s (dev: 1 per 5s)
→ generate new verification token
→ send email
← 200 { message: "Verification email sent" }
Status: Implemented in D2-T03 (stub: session cookie)
POST /auth/signin { email, password }
→ check email_verified (403 if false)
→ check account lockout (403 if locked: 5 failed attempts → 15-min lockout)
→ Argon2id verify password
→ on success: INSERT np_sessions (expires_at = now + 7 days)
→ issue JWT (RS256, kid = AUTH_JWT_KEY_ID)
payload: { sub: user_id, email, tenant_id?, source_account_id, exp, iat, session_id }
Hasura claims namespace: https://hasura.io/jwt/claims
X-Hasura-User-Id, X-Hasura-Default-Role, X-Hasura-Allowed-Roles
X-Hasura-Source-Account-Id, X-Hasura-Tenant-Id (if Cloud)
→ set cookie: HttpOnly; SameSite=Lax; Secure; Path=/; Max-Age=604800; Domain=.nself.org
← 200 { user: { id, email, email_verified } }
Status: Implemented in D2-T04
POST /auth/forgot-password { email }
→ always return 200 (no email enumeration)
→ generate code_verifier (32 random bytes, base64url)
→ code_challenge = BASE64URL(SHA256(code_verifier))
→ INSERT np_password_reset_tokens (code_challenge, expires_at = now + 15 min)
→ send reset email with link containing code_verifier
← 200 { message: "If that email is registered, a reset link was sent" }
POST /auth/reset-password { code_verifier, new_password }
→ recompute code_challenge = BASE64URL(SHA256(code_verifier))
→ look up token WHERE code_challenge = $1 AND used_at IS NULL AND expires_at > now
→ validate (400 if not found, expired, or already used)
→ Argon2id hash new_password
→ UPDATE np_users SET hashed_password = new_hash
→ UPDATE np_password_reset_tokens SET used_at = NOW()
→ REVOKE ALL SESSIONS: UPDATE np_sessions SET revoked_at = NOW() WHERE user_id = $1 AND revoked_at IS NULL
← 200 { message: "Password reset. Please sign in with your new password." }
Status: Implemented in D2-T05
POST /auth/mfa/enroll (authenticated)
→ generate base32 TOTP secret
→ AES-256-GCM encrypt secret
→ INSERT np_mfa_devices (device_type='totp', totp_secret_enc, verified_at=NULL)
→ generate 8 backup codes; hash each; AES-256-GCM encrypt array
→ INSERT np_mfa_devices (device_type='backup_codes', backup_codes_enc)
← 200 { qr_code_url, secret_b32, backup_codes_plaintext }
POST /auth/mfa/verify (during signin challenge)
→ lookup active TOTP device for user (disabled_at IS NULL, verified_at IS NOT NULL)
→ verify submitted TOTP code against decrypted secret (RFC 6238, ±30s window)
→ rate-limit: 5 failures → 5-min lockout
→ on first verify after enrollment: set verified_at = NOW()
← 200 (session elevated to MFA-verified)
POST /auth/mfa/disable (authenticated, re-auth required)
→ verify current password or valid TOTP
→ UPDATE np_mfa_devices SET disabled_at = NOW() WHERE user_id = $1
← 200 { message: "MFA disabled" }
Status: Implemented in D2-T12
GET /auth/oauth/google (or /auth/oauth/github)
→ generate state = 32 random bytes
→ state_hash = SHA256(state), hex-encoded
→ INSERT np_oauth_states (state_hash, provider, expires_at = now + 10 min)
→ redirect to provider with state param
GET /auth/callback/:provider?code=...&state=...
→ recompute state_hash = SHA256(state)
→ SELECT from np_oauth_states WHERE state_hash = $1 AND expires_at > now
→ 400 "CSRF validation failed" if not found or expired
→ exchange code for OAuth tokens
→ upsert np_users (email from provider, email_verified = TRUE for verified providers)
→ issue session + JWT (same flow as signin)
← redirect to AUTH_MAGIC_LINK_BASE_URL (allowlisted paths only)
redirect_uri allowlist: only api.nself.org/auth/callback/* paths. No wildcards.
- Generate new RSA 2048-bit keypair:
openssl genrsa -out new-key.pem 2048 - Extract public key:
openssl rsa -in new-key.pem -pubout -out new-pub.pem - Bump
AUTH_JWT_KEY_IDin.env.prod(e.g.,prod-key-YYYY-MM-DD) - Update
AUTH_JWT_PRIVATE_KEY+AUTH_JWT_PUBLIC_KEYin.env.secrets - Add new public key to JWKS endpoint alongside the old key (24h grace period)
- After 24h: remove old key from JWKS
- Run
nself build && nself deploy prod
The JWKS endpoint is at https://auth.nself.org/.well-known/jwks.json. Hasura is configured with {"type":"RS256","jwk_url":"https://auth.nself.org/.well-known/jwks.json"} so it auto-rotates when the JWKS updates.
Sessions are stored in np_sessions with a revoked_at column. The auth middleware checks revoked_at IS NULL on every authenticated request — this means even a valid unexpired JWT will return 401 if the session was server-side revoked.
Revocation triggers:
- Password reset: all sessions for the user
- Sign-out-all: all sessions for the user
- Admin revocation: specific session by ID
JWT payload includes Hasura claims in the namespace https://hasura.io/jwt/claims:
{
"https://hasura.io/jwt/claims": {
"x-hasura-user-id": "<np_users.id>",
"x-hasura-default-role": "nself_user",
"x-hasura-allowed-roles": ["nself_user", "nself_admin"],
"x-hasura-source-account-id": "<source_account_id>",
"x-hasura-tenant-id": "<tenant_id or null>"
}
}Hasura row filters use X-Hasura-User-Id for np_users and np_sessions. Cloud tables also filter by X-Hasura-Tenant-Id.
- plugin-auth.md — plugin install instructions
- Config-Auth.md — env var reference
-
web/backend/migrations/025_np_auth_plugin_schema.sql— schema source -
web/backend/plugins/auth/plugin.yaml— plugin config
ɳSelf CLI v1.0.9. MIT licensed. Docs CC BY 4.0.
GitHub · Issues · Discussions · nself.org · docs.nself.org
Getting Started
Commands
- Commands, Overview
- Lifecycle: cmd-init · cmd-build · cmd-start · cmd-stop · cmd-restart · cmd-dev
- Monitoring: cmd-status · cmd-logs · cmd-health · cmd-urls · cmd-doctor · cmd-monitor · cmd-alerts · cmd-sentry · cmd-watchdog · cmd-dogfood
- Data: cmd-db · cmd-backup · cmd-dr · cmd-queue · cmd-webhooks
- Config: cmd-config · cmd-service · cmd-env · cmd-promote
- Networking: cmd-ssl · cmd-trust · cmd-dns-setup
- Security: cmd-security · cmd-secrets · cmd-waf
- Tenancy: cmd-tenant · cmd-billing
- Plugins: cmd-plugin · cmd-license
- AI: cmd-ai · cmd-claw · cmd-model
- Templates: cmd-template
- Utilities: cmd-exec · cmd-clean · cmd-reset · cmd-update · cmd-upgrade · cmd-version · cmd-admin · cmd-migrate · cmd-migrate-firebase · cmd-migrate-supabase · cmd-completion
Features
- Features, Overview
- Feature-Auth
- Feature-Storage
- Feature-Search
- Feature-Functions
- Feature-Email
- Feature-Monitoring
- Feature-Plugins
- Feature-ɳClaw, AI Assistant
- Feature-ɳChat, Messaging
- Feature-ɳTV, Media Player
- Feature-ɳFamily, Family Social
- Feature-ɳCloud, Managed Hosting
- Feature-Memory-Rooms, Knowledge Organization
- Feature-Agent-Dashboard, Agent Metrics
- Feature-Image-Generation, AI Image Generation
Configuration
- Configuration, Overview
- Config-Env-Vars
- Config-Postgres
- Config-Hasura
- Config-Auth
- Config-Nginx
- Config-Optional-Services
- Config-Custom-Services
- Config-System
Plugins (87 + 10 monitoring)
Free (25)
- plugin-backup
- plugin-content-acquisition
- plugin-content-progress
- plugin-cron
- plugin-donorbox
- plugin-feature-flags
- plugin-github
- plugin-github-runner
- plugin-invitations
- plugin-jobs
- plugin-link-preview
- plugin-mdns
- plugin-mlflow
- plugin-monitoring
- plugin-notifications
- plugin-notify
- plugin-paypal
- plugin-search
- plugin-shopify
- plugin-stripe
- plugin-subtitle-manager
- plugin-tokens
- plugin-torrent-manager
- plugin-vpn
- plugin-webhooks
Pro (62)
- plugin-access-controls
- plugin-activity-feed
- plugin-admin-api
- nself-ai-gateway
- nself-ai-cc
- nself-ai-mcp
- plugin-analytics
- plugin-auth
- plugin-backup-pro
- plugin-bots
- plugin-browser
- plugin-calendar
- plugin-cdn
- plugin-chat
- plugin-claw
- plugin-claw-budget
- plugin-claw-news
- plugin-claw-web
- plugin-cloudflare
- plugin-cms
- plugin-compliance
- plugin-cron-pro
- plugin-ddns
- plugin-devices
- plugin-documents
- plugin-donorbox-pro
- plugin-entitlements
- plugin-epg
- plugin-file-processing
- plugin-game-metadata
- plugin-geocoding
- plugin-geolocation
- plugin-google
- plugin-home
- plugin-idme
- plugin-knowledge-base
- plugin-linkedin
- plugin-livekit
- plugin-media-processing
- plugin-meetings
- plugin-moderation
- plugin-mux
- plugin-notify-pro
- plugin-object-storage
- plugin-observability
- plugin-paypal-pro
- plugin-photos
- plugin-podcast
- plugin-post
- plugin-realtime
- plugin-recording
- plugin-retro-gaming
- plugin-rom-discovery
- plugin-shopify-pro
- plugin-social
- plugin-sports
- plugin-stream-gateway
- plugin-streaming
- plugin-stripe-pro
- plugin-support
- plugin-tmdb
- plugin-voice
- plugin-web3
- plugin-workflows
Planned (26)
plugin-auditplugin-blogplugin-checkoutplugin-commerceplugin-drmplugin-exportplugin-flowplugin-importplugin-ldapplugin-mailgunplugin-mediaplugin-oauth-providersplugin-pagesplugin-postmarkplugin-rate-limitplugin-reportsplugin-samlplugin-schedulerplugin-sendgridplugin-ssoplugin-subscriptionplugin-thumbplugin-transcoderplugin-twilioplugin-wafplugin-watermark
Guides
- Guide-Production-Deployment
- Guide-SSL-Setup
- Guide-Multi-Tenancy
- Guide-Security-Hardening
- Guide-Monitoring-Setup
- Guide-Backup-Restore
- Guide-Custom-Services
- Guide-Migration-from-v1
Architecture
Reference
- API-Reference
- reference-error-codes, Error Codes
Licensing
Security
Brand
Operations
- operations/release-cascade, Release Cascade
- operations/self-healing, Self-Healing Schema
- operations/redis-tuning, Redis Pool Tuning
- operations/meilisearch-warmup, MeiliSearch Warm-Up
- operations/jwt-rotation, JWT Key Rotation
- operations/windows-wsl2-setup, Windows / WSL2 Setup
- operations/gemini-oauth-reauth, Gemini OAuth Reauth
Contributing
Admin
- USER-ACTION-QUEUE, Pending Admin Actions