Skip to content

puzed/darkauth

Repository files navigation

DarkAuth

DarkAuth screenshot

A zero-knowledge authentication system with OIDC compatibility. DarkAuth implements OPAQUE (RFC 9380) for password authentication where the server never learns the password, and provides optional zero-knowledge delivery of Data Root Keys (DRK) to trusted clients.

DarkAuth is open source and self-hosted. There is no paid plan, subscription, or cloud service. A ready-to-run Docker image is available at ghcr.io/puzed/darkauth:latest.

Features

  • Zero-Knowledge Password Auth: OPAQUE protocol ensures passwords never reach the server
  • OIDC Compatible: Standard OAuth 2.0/OpenID Connect for universal compatibility
  • Zero-Knowledge DRK Delivery: Optional fragment-based JWE delivery for trusted clients
  • TOTP MFA: Time-based one-time passwords for users and admins with backup codes, rate limits, and per-organization enforcement
  • Email Password Reset: SMTP-gated self-service reset links with hashed one-time tokens and session invalidation
  • Database-Backed Configuration: Most settings stored in PostgreSQL; minimal config.yaml for bootstrap
  • Two-Port Architecture: Separate ports for user (9080) and admin (9081). First-run installer is served on the admin port until setup completes.
  • Secure Key Storage: Optional encryption of private keys at rest using Argon2id-derived KEK
  • RBAC Support: Fine-grained permissions and organization-scoped roles for users
  • Production Ready: CSP headers, rate limiting, session management

Quick Start

Run with Docker

docker run -d -p 9080:9080 -p 9081:9081 ghcr.io/puzed/darkauth:latest

Then visit http://localhost:9081 to complete installation.

Prerequisites

  • Node.js 24+
  • PostgreSQL 15+ (or use embedded PGLite)
  • Docker & Docker Compose (optional, for PostgreSQL or non-Docker setups)

Installation Options

DarkAuth supports multiple database options:

  1. Remote PostgreSQL - Connect to an existing PostgreSQL instance
  2. Embedded PGLite - Use the built-in PGLite database (no external dependencies)

1. Start PostgreSQL (if using remote PostgreSQL)

Using Docker Compose:

docker-compose up -d

Or use an existing PostgreSQL instance and configure the postgresUri in config.yaml.

2. Install Dependencies

npm install

3. Run Database Migrations

npm run db:push

4. Configuration

Create a config.yaml file in the project root:

# Database configuration (choose one)
# Option 1: Remote PostgreSQL
dbMode: remote
postgresUri: postgresql://username:password@localhost:5432/darkauth

# Option 2: Embedded PGLite
# dbMode: pglite
# pgliteDir: ./data/pglite

# Server ports
userPort: 9080
adminPort: 9081

# UI proxy (development only)
proxyUi: false

# Key encryption passphrase (required for secure mode)
kekPassphrase: "your-strong-passphrase"

5. Initial Setup

Option A: Interactive Web Installer (Recommended)

npm start

Visit the installation URL shown in the console (includes a one-time token).

The installer will guide you through:

  1. Database selection (PostgreSQL or PGLite)
  2. KEK passphrase setup
  3. Admin user creation

Option B: CLI Installation

npm run install:script

6. Start the Server

npm start

7. Access the System

Development Mode

Run with hot-reloading and Vite dev servers:

# Set proxyUi: true in config.yaml for proxied development
npm run dev

This runs all three services concurrently:

  • API server with hot-reloading
  • User UI on Vite dev server
  • Admin UI on Vite dev server

Architecture

Port Allocation

  • 9080: User-facing OIDC/Auth endpoints and UI
  • 9081: Admin interface and API (also serves the installation wizard until setup completes)

Project Structure

DarkAuth is organized as a monorepo with npm workspaces:

packages/
├── api/           # Main server and API
├── user-ui/       # User-facing React application
├── admin-ui/      # Admin panel React application  
├── test-suite/    # Playwright end-to-end tests
└── ...

URL Structure

  • /api/*: Backend API endpoints
  • /: React UI applications

Database Schema

All configuration and state stored in PostgreSQL:

  • settings: System configuration
  • jwks: Signing keys (EdDSA/Ed25519)
  • clients: OAuth/OIDC client registrations
  • users: User accounts
  • opaque_records: OPAQUE authentication data
  • wrapped_root_keys: Encrypted DRK storage
  • auth_codes: Authorization codes
  • sessions: Active sessions
  • password_reset_tokens: HMAC-hashed, single-use email password reset tokens
  • otp_configs / otp_backup_codes: OTP configuration and backup codes
  • pending_auth: In-progress auth requests
  • admin_users: Admin accounts (separate cohort)
  • permissions/groups: RBAC configuration
  • audit_logs: Audit trail of system events

Default Clients

Two clients are created during installation:

demo-public-client (Public Client)

demo-confidential-client (Confidential Client)

Security Modes

  • Private keys are always encrypted at rest using KEK
  • KEK derived from passphrase using Argon2id
  • Passphrase configured in config.yaml
  • KDF parameters stored in database

Configuration

All configuration is managed via config.yaml. Create this file in the project root:

# Database configuration (required)
dbMode: remote | pglite
postgresUri: postgresql://username:password@localhost:5432/darkauth  # For remote mode
pgliteDir: ./data/pglite  # For embedded mode

# Server ports (optional, with defaults)
userPort: 9080            # User/OIDC server port
adminPort: 9081           # Admin server port  
proxyUi: false            # Proxy to Vite dev servers (development only)

# Security (required for secure mode)
kekPassphrase: "your-strong-passphrase"

# Optional (with defaults)
publicOrigin: "http://localhost:9080"  # Public-facing origin
issuer: "http://localhost:9080"        # OIDC issuer URL
rpId: "localhost"         # Relying party identifier

API Endpoints

OIDC Discovery

  • GET /api/.well-known/openid-configuration
  • GET /api/.well-known/jwks.json

Authorization

  • GET /api/authorize - OAuth authorization endpoint
  • POST /api/authorize/finalize - Complete authorization (internal)
  • POST /api/token - Token exchange endpoint
  • GET/POST /api/userinfo - OIDC UserInfo endpoint for bearer access tokens
  • POST /api/introspect - OAuth token introspection for confidential clients
  • POST /api/revoke - OAuth refresh token revocation

Authorization codes are short-lived and single-use. Redemption at the token endpoint is enforced atomically so concurrent redemption attempts cannot both succeed.

OPAQUE Authentication

  • POST /api/opaque/register/start
  • POST /api/opaque/register/finish
  • POST /api/opaque/login/start
  • POST /api/opaque/login/finish

Email Password Reset

  • POST /api/password/reset/request - Request reset email with generic anti-enumeration response
  • GET /api/password/reset/token?token=... - Validate reset link and return only masked email
  • POST /api/password/reset/start - Start OPAQUE reset registration with reset token
  • POST /api/password/reset/finish - Finish reset, consume token, replace password record, and revoke sessions

OTP (TOTP) — User

  • POST /api/otp/setup/init
  • POST /api/otp/setup/verify
  • GET /api/otp/status
  • POST /api/otp/verify

OTP (TOTP) — Admin

  • POST /api/admin/otp/setup/init
  • POST /api/admin/otp/setup/verify
  • GET /api/admin/otp/status
  • POST /api/admin/otp/verify

DRK Management

  • GET /api/crypto/wrapped-drk - Retrieve wrapped DRK
  • PUT /api/crypto/wrapped-drk - Store wrapped DRK

Session

  • GET /api/session - Current session info
  • POST /api/logout - End session

Refresh tokens are stored hashed at rest and rotated as single-use credentials. Rotation is enforced atomically so concurrent redemption attempts cannot both succeed.

When OTP is enabled and required by the user organization policy (organizations.force_otp=true), login creates a partial session with data.otp_required=true. After successful OTP verification, the session includes data.otp_verified=true. AMR includes otp and ACR is urn:ietf:params:acr:mfa.

Admin API (Port 9081)

  • /api/admin/users - User management
  • /api/admin/clients - Client management
  • /api/admin/settings - System settings
  • /api/admin/jwks - Key management

Changelog

  • Markdown entries live in changelog/ as vX.Y.Z.md
  • Changelog JSON is published to https://release.darkauth.com/changelog.json

Zero-Knowledge Flow

Registration

  1. Client generates OPAQUE registration request
  2. Server stores opaque envelope (never sees password)
  3. Client derives stable export_key from password
  4. Client generates random DRK (32 bytes)
  5. DRK wrapped with key derived from export_key
  6. Wrapped DRK stored on server

Login with ZK Delivery

  1. Client includes ephemeral ECDH public key in /authorize
  2. OPAQUE login produces export_key
  3. Client unwraps DRK using export_key
  4. DRK encrypted to app's ephemeral key (JWE)
  5. JWE delivered via URL fragment (never hits server)
  6. App verifies hash binding and decrypts DRK

Email Password Reset and Encrypted Data

Email reset restores account access by creating a new OPAQUE password record. It does not decrypt DRK-wrapped data that depends on the old password-derived export key. After reset, users may need to use the existing old-password recovery flow or generate new keys.

Building for Production

# Build all packages
npm run build

# Type checking
npm run typecheck

# Linting
npm run lint

# Code formatting
npm run format

# Start production server (ensure config.yaml is configured)
npm start

Testing

The project uses Playwright for end-to-end testing:

# Install Playwright browsers (first time only)
npm run test:install

# Run all tests
npm test

# Run with detailed output
npm run test:report

# Run in headed mode (with browser UI)
npm run test:headed

# Debug tests
npm run test:debug

Security Considerations

  1. Always use HTTPS in production - Cookies are marked Secure
  2. Strong KEK passphrase - Use 32+ characters for production in config.yaml
  3. Secure config.yaml - Protect the config file as it contains the KEK passphrase
  4. Database security - Encrypt PostgreSQL connections and storage
  5. CSP Headers - Strict Content Security Policy enforced
  6. Rate limiting - Configurable per endpoint
  7. Session security - Short-lived sessions with CSRF protection
  8. OTP hardening - Secrets encrypted with KEK, backup codes hashed with Argon2, anti-replay via timestep tracking, cohort/organization enforcement, AMR/ACR reflect MFA
  9. Password reset - Enable only with working SMTP; reset requests use generic responses, HMAC-hashed one-time tokens, rate limits, audit events, and post-reset session revocation

Support

For issues and feature requests, please use the GitHub issue tracker.

License

  • Core and most packages are licensed under AGPL-3.0. See the root LICENSE and individual package LICENSE files.
  • packages/demo-app and packages/darkauth-client are licensed under MIT.
  • packages/opaque-ts is licensed under BSD-3-Clause.

Refer to each package's package.json and LICENSE file for the authoritative license.

About

A zero-knowledge authentication system with OIDC compatibility

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors