https://github.com/puzed/darkauth
A zero-knowledge authentication system with OIDC compatibility
https://github.com/puzed/darkauth
oauth oidc opaque rfc-9380 zero-knowledge
Last synced: 8 days ago
JSON representation
A zero-knowledge authentication system with OIDC compatibility
- Host: GitHub
- URL: https://github.com/puzed/darkauth
- Owner: puzed
- License: other
- Created: 2025-09-03T18:06:50.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2026-05-23T20:20:27.000Z (15 days ago)
- Last Synced: 2026-05-23T22:22:42.825Z (15 days ago)
- Topics: oauth, oidc, opaque, rfc-9380, zero-knowledge
- Language: TypeScript
- Homepage: https://darkauth.com
- Size: 27.5 MB
- Stars: 6
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# DarkAuth

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
```bash
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:
```bash
docker-compose up -d
```
Or use an existing PostgreSQL instance and configure the `postgresUri` in `config.yaml`.
### 2. Install Dependencies
```bash
npm install
```
### 3. Run Database Migrations
```bash
npm run db:push
```
### 4. Configuration
Create a `config.yaml` file in the project root:
```yaml
# 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)
```bash
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
```bash
npm run install:script
```
### 6. Start the Server
```bash
npm start
```
### 7. Access the System
- **User/OIDC**: http://localhost:9080 (or configured `userPort`)
- **Admin Panel**: http://localhost:9081 (or configured `adminPort`)
- **OIDC Discovery**: http://localhost:9080/api/.well-known/openid-configuration
## Development Mode
Run with hot-reloading and Vite dev servers:
```bash
# 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)
- **Type**: Public
- **PKCE**: Required
- **ZK Delivery**: fragment-jwe (enabled)
- **Redirect URIs**:
- http://localhost:9092/
- http://localhost:9092/callback
- http://localhost:3000/
- http://localhost:3000/callback
- https://app.example.com/
- https://app.example.com/callback
### demo-confidential-client (Confidential Client)
- **Type**: Confidential
- **Auth Method**: client_secret_basic
- **Grant Types**: authorization_code, refresh_token, client_credentials
- **ZK Delivery**: None
- **Redirect URIs**:
- http://localhost:4000/callback
- https://support.example.com/callback
## 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:
```yaml
# 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
```bash
# 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:
```bash
# 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.