Developer Documentation

Everything you need to implement ZenoAuth

From 5-minute quick start to advanced deployment configurations. Comprehensive guides, API references, and real-world examples.

🚀

Quick Start

Get ZenoAuth running in under 5 minutes with Docker or manual installation.

Start Here
🔑

Multi-Factor Auth

Configure TOTP-based MFA with backup codes for enhanced account security.

Setup MFA
🌐

SSO Providers

Connect external identity providers like Google, Microsoft, GitHub, and Okta.

Configure SSO
👥

SCIM Provisioning

Configure SCIM v2 user and group provisioning for enterprise identity management.

Setup SCIM
🔄

Key Management

Manage Ed25519 signing keys with rotation, JWKS distribution, and revocation.

Manage Keys
🔧

Admin Interface

Modern Next.js admin dashboard for user management, analytics, and audit logging.

Configure
📡

API Reference

Complete REST API documentation with OAuth 2.0 and OpenID Connect endpoints.

View APIs
🏢

Groups & Permissions

Manage groups, roles, and permissions for organization-wide access control.

Configure Groups
🔧

Dynamic Client Registration

Self-service OAuth client registration with programmatic lifecycle management.

View DCR Docs
🎯

Rich Authorization Requests

Fine-grained authorization with structured permission details beyond scopes.

View RAR Docs

Quick Start Guide

Option 1: Cloud SaaS (Fastest)

Cloud Setup
# Start your free trial at zenoauth.com 1. Sign up at https://zenoauth.com/signup 2. Create your first organization 3. Configure your OAuth clients # Your endpoints will be: API: https://yourorg.zenoauth.com/api Admin: https://yourorg.zenoauth.com/admin OAuth: https://yourorg.zenoauth.com/oauth

Option 2: Self-Hosted (Docker)

Self-Hosted Setup
# Download from customer portal after license purchase # Contact sales@cloudfragments.com for access # Environment setup export ZENOAUTH__DATABASE__URL="postgres://user:pass@localhost:5432/zenoauth" export ZENOAUTH__SERVER__BASE_URL="http://localhost:3051" export ZENOAUTH__LICENSE_KEY="your-license-key" # Start with Docker Compose docker-compose up -d # Services available: API Server: http://localhost:3051 Admin UI: http://localhost:3050

First Steps After Installation

  1. Access Admin Interface: Navigate to http://localhost:3050
  2. Create Admin User: Register your first admin user via the UI
  3. Register OAuth Client: Create your first OAuth client application
  4. Test Authentication: Test login via /auth/login endpoint
  5. Configure SCIM: Set up SCIM provisioning for your identity provider

API Reference

Authentication Endpoints

Core Auth APIs
# User Registration POST /auth/register Content-Type: application/json { "email": "user@example.com", "password": "secure_password", "first_name": "John", "last_name": "Doe" } # User Login POST /auth/login Content-Type: application/json { "email": "user@example.com", "password": "secure_password" } # Response { "access_token": "eyJ...", "refresh_token": "rt_...", "expires_in": 3600, "token_type": "Bearer" }

OAuth 2.0 Endpoints

OAuth 2.0 APIs
# Authorization Endpoint GET /oauth/authorize? response_type=code& client_id=your_client_id& redirect_uri=https://yourapp.com/callback& scope=openid profile email& state=random_state # Token Endpoint POST /oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& code=auth_code_from_callback& client_id=your_client_id& client_secret=your_client_secret& redirect_uri=https://yourapp.com/callback # Token Response { "access_token": "eyJ...", "id_token": "eyJ...", "refresh_token": "rt_...", "expires_in": 3600, "token_type": "Bearer", "scope": "openid profile email" }

Pushed Authorization Requests (PAR)

PAR (RFC 9126) enhances OAuth security by allowing clients to push authorization parameters directly to the server via a secure back-channel, eliminating URL-based parameter exposure and tampering risks.

PAR Flow (Two-Step Process)
# Step 1: Push Authorization Request POST /oauth/par Authorization: Basic base64(client_id:client_secret) Content-Type: application/x-www-form-urlencoded response_type=code& redirect_uri=https://yourapp.com/callback& scope=openid profile email& state=random_state& code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM& code_challenge_method=S256 # PAR Response (60-second lifetime) { "request_uri": "urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c", "expires_in": 60 } # Step 2: Authorization Request (simplified) GET /oauth/authorize? client_id=your_client_id& request_uri=urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c # Benefits: • Enhanced Security - Parameters never in browser URL • Prevents Tampering - Server-side parameter storage • No URL Limits - Supports large payloads • Single-Use - request_uri works only once

Dynamic Client Registration (DCR)

DCR (RFC 7591) enables self-service OAuth client registration. Clients can register themselves at runtime without manual admin intervention, receiving management tokens for future updates.

DCR - Self-Service Client Registration
# Step 1: Register New Client (No Auth Required) POST /oauth/register Content-Type: application/json { "client_name": "My Application", "redirect_uris": ["https://myapp.com/callback"], "grant_types": ["authorization_code", "refresh_token"], "token_endpoint_auth_method": "client_secret_basic", "scope": "openid profile email" } # Registration Response { "client_id": "dyn_abc123...", "client_secret": "secret_xyz...", "registration_access_token": "rat_token123...", "registration_client_uri": "https://auth.example.com/oauth/register/dyn_abc123", "client_id_issued_at": 1234567890, "client_secret_expires_at": 0 } # Step 2: Read Client Configuration GET /oauth/register/dyn_abc123 Authorization: Bearer rat_token123... # Step 3: Update Client Metadata PUT /oauth/register/dyn_abc123 Authorization: Bearer rat_token123... Content-Type: application/json { "client_name": "Updated App Name", "redirect_uris": ["https://myapp.com/callback", "https://myapp.com/callback2"] } # Step 4: Delete Client Registration DELETE /oauth/register/dyn_abc123 Authorization: Bearer rat_token123... # Benefits: • Self-Service - No admin intervention needed • Secure Management - Token-based client updates • API-Driven - Programmatic client lifecycle • RFC 7591 Compliant - Standard protocol

Rich Authorization Requests (RAR)

RAR (RFC 9396) provides fine-grained authorization beyond simple scopes. Request structured permissions for specific actions on specific resources with detailed authorization_details.

RAR - Fine-Grained Authorization
# Authorization with Structured Details POST /oauth/par Authorization: Basic base64(client_id:client_secret) Content-Type: application/x-www-form-urlencoded response_type=code& redirect_uri=https://myapp.com/callback& scope=openid& authorization_details=[ { "type": "payment_initiation", "actions": ["initiate", "status", "cancel"], "locations": ["https://bank.example.com/payments"], "instructedAmount": { "currency": "EUR", "amount": "123.50" } }, { "type": "account_information", "actions": ["read_balances", "read_transactions"], "locations": ["https://bank.example.com/accounts/12345"] } ] # Token Response with Authorization Details { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600, "authorization_details": [ { "type": "payment_initiation", "actions": ["initiate", "status", "cancel"], "locations": ["https://bank.example.com/payments"], "instructedAmount": { "currency": "EUR", "amount": "123.50" } } ] } # Supported Detail Types: • payment_initiation - Financial transactions • account_information - Account data access • resource_access - API resource permissions • api_access - Fine-grained API scoping # Benefits: • Fine-Grained - Specific actions on specific resources • Structured - JSON objects with type safety • Consent Clarity - Users see exact permissions • RFC 9396 Compliant - Standard protocol

OpenID Connect

OIDC Discovery
# Discovery Endpoint GET /.well-known/openid-configuration # Response (partial) { "issuer": "https://auth.yourdomain.com", "authorization_endpoint": "/oauth/authorize", "token_endpoint": "/oauth/token", "userinfo_endpoint": "/oauth/userinfo", "jwks_uri": "/oauth/jwks", "scopes_supported": [ "openid", "profile", "email" ], "response_types_supported": [ "code", "token", "id_token" ] } # User Info Endpoint GET /oauth/userinfo Authorization: Bearer eyJ... # Response { "sub": "user_id", "email": "user@example.com", "email_verified": true, "name": "John Doe" }

Admin APIs

Administration
# Create OAuth Client POST /admin/clients Authorization: Bearer admin_token Content-Type: application/json { "name": "My App", "redirect_uris": [ "https://myapp.com/callback" ], "allowed_scopes": [ "openid", "profile", "email" ] } # List Users GET /admin/users Authorization: Bearer admin_token # Get Analytics GET /api/v1/analytics Authorization: Bearer admin_token # Get Audit Logs GET /api/v1/audit Authorization: Bearer admin_token

Configuration Guide

Configuration Methods

ZenoAuth supports three configuration methods in order of precedence:

1. Environment Variables

Highest priority. Use ZENOAUTH__SECTION__KEY format.

Environment Variables
export ZENOAUTH__SERVER__PORT=3051 export ZENOAUTH__DATABASE__URL="postgres://..." # Ed25519 keys auto-generated

2. Configuration File

TOML format configuration file at zenoauth.toml.

zenoauth.toml
[server] port = 3051 bind_address = "0.0.0.0" [database] url = "postgres://..." max_connections = 20 [oauth] issuer = "https://auth.example.com"

3. Command Line

Lowest priority. Useful for development and testing.

Command Line
./zenoauth \ --server.port 3051 \ --database.url "postgres://..." \ --config zenoauth.toml

Complete Configuration Reference

Complete zenoauth.toml
# Server Configuration [server] bind_address = "0.0.0.0" port = 8080 base_url = "https://auth.yourdomain.com" workers = 0 # 0 = auto-detect CPU cores # Database Configuration [database] url = "postgres://zenoauth:password@localhost:5432/zenoauth" max_connections = 20 min_connections = 5 idle_timeout_seconds = 300 # Security Configuration [security] jwt_secret = "your-super-secret-jwt-key-at-least-32-characters" session_timeout_seconds = 3600 password_min_length = 8 max_login_attempts = 5 lockout_duration_seconds = 900 # OAuth Configuration [oauth] default_token_lifetime_seconds = 3600 refresh_token_lifetime_seconds = 86400 enable_pkce = true require_pkce = false # Rate Limiting [rate_limiting] enabled = true requests_per_minute = 60 burst_size = 10 # Logging Configuration [logging] level = "info" format = "json" correlation_ids = true # SMTP for Email (Optional) [smtp] host = "smtp.gmail.com" port = 587 username = "your-email@gmail.com" password = "your-password" from_address = "noreply@yourdomain.com"

Deployment Guide

Docker Deployment

Production Docker
# docker-compose.prod.yml version: '3.8' services: zenoauth: image: zenoauth:latest ports: - "3051:3051" environment: - ZENOAUTH__DATABASE__URL=postgres://zenoauth:pass@postgres:5432/zenoauth - ZENOAUTH__SERVER__BASE_URL=https://auth.yourdomain.com # Ed25519 keys auto-generated on startup depends_on: - postgres restart: unless-stopped postgres: image: postgres:15 environment: - POSTGRES_DB=zenoauth - POSTGRES_USER=zenoauth - POSTGRES_PASSWORD=${DB_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data restart: unless-stopped volumes: postgres_data:

Kubernetes Deployment

Kubernetes Manifest
# zenoauth-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: zenoauth spec: replicas: 3 selector: matchLabels: app: zenoauth template: metadata: labels: app: zenoauth spec: containers: - name: zenoauth image: zenoauth:latest ports: - containerPort: 3051 env: - name: ZENOAUTH__DATABASE__URL valueFrom: secretKeyRef: name: zenoauth-secrets key: database-url - name: ZENOAUTH__SERVER__BASE_URL value: "https://auth.yourdomain.com" # Ed25519 signing keys auto-generated resources: requests: memory: "64Mi" cpu: "100m" limits: memory: "128Mi" cpu: "500m" readinessProbe: httpGet: path: /health port: 3051 initialDelaySeconds: 10 periodSeconds: 5 livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 15 periodSeconds: 10

Production Checklist

Security

  • ✅ Enable HTTPS/TLS 1.3
  • ✅ Configure proper JWT secrets
  • ✅ Set up rate limiting
  • ✅ Enable audit logging
  • ✅ Configure CORS policies
  • ✅ Set secure session timeouts

Performance

  • ✅ Optimize database connections
  • ✅ Enable connection pooling
  • ✅ Configure caching policies
  • ✅ Set up monitoring
  • ✅ Configure load balancing
  • ✅ Enable health checks

Multi-Factor Authentication (MFA)

TOTP-Based MFA

ZenoAuth provides RFC 6238 compliant TOTP (Time-based One-Time Password) authentication with backup codes for account recovery.

Initialize MFA Setup

POST /auth/mfa/setup/init
# Response { "secret": "JBSWY3DPEHPK3PXP", "qr_code_uri": "otpauth://totp/ZenoAuth:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=ZenoAuth", "issuer": "ZenoAuth" } # Render QR code for authenticator app

Complete MFA Setup

POST /auth/mfa/setup/verify
{ "code": "123456" } # Response with backup codes { "success": true, "backup_codes": [ "A1B2C3D4", "E5F6G7H8", "I9J0K1L2", "M3N4O5P6", "Q7R8S9T0", "U1V2W3X4", "Y5Z6A7B8", "C9D0E1F2", "G3H4I5J6", "K7L8M9N0" ] }

MFA Login Flow

Two-Step Login
# Step 1: Initial login returns MFA challenge POST /auth/login { "email": "user@example.com", "password": "password" } # Response when MFA is enabled { "mfa_required": true, "mfa_token": "eyJhbGciOiJFZDI1NTE5...", "message": "MFA verification required" } # Step 2: Verify MFA code POST /auth/mfa/verify { "mfa_token": "eyJhbGciOiJFZDI1NTE5...", "code": "123456" } # Response with tokens { "access_token": "eyJhbGciOiJFZDI1NTE5...", "refresh_token": "...", "token_type": "Bearer", "expires_in": 3600 }

MFA Management APIs

GET /auth/mfa/status

Check MFA status
and backup codes remaining

POST /auth/mfa/disable

Disable MFA
(requires current TOTP code)

POST /auth/mfa/backup-codes/regenerate

Generate new backup codes
(invalidates previous)

External SSO Providers

Configure External Identity Providers

Connect OIDC and OAuth 2.0 providers like Google, Microsoft, GitHub, and Okta for seamless Single Sign-On.

POST /api/v1/sso/providers
{ "name": "google", "display_name": "Google", "provider_type": "oidc", "client_id": "your-google-client-id.apps.googleusercontent.com", "client_secret": "your-google-client-secret", "issuer_url": "https://accounts.google.com", "scopes": ["openid", "profile", "email"], "button_text": "Continue with Google", "icon_url": "https://example.com/icons/google.svg", "button_color": "#4285F4", "auto_provision_users": true, "allow_account_linking": true, "enabled": true }

Initiate SSO Login

SSO Flow
# List available providers GET /oauth/sso/providers # Initiate SSO (redirects to provider) GET /oauth/sso/google?redirect_uri=https://app.com/callback # Callback returns tokens GET /oauth/sso/google/callback?code=...&state=...

Supported Providers

  • Google (OIDC)
  • Microsoft / Azure AD (OIDC)
  • GitHub (OAuth 2.0)
  • Okta (OIDC)
  • Any OIDC/OAuth 2.0 provider

Signing Key Management

Ed25519 JWT Signing Keys

ZenoAuth uses Ed25519 elliptic curve cryptography for JWT signing with full key lifecycle management.

Key Operations

Key Management APIs
# List all signing keys GET /api/v1/keys # Get current signing key GET /api/v1/keys/current # Create new key POST /api/v1/keys { "kid": "key-2024-06-01", "activate_immediately": false } # Rotate to new key POST /api/v1/keys/{key_id}/rotate # Revoke key (emergency) POST /api/v1/keys/{key_id}/revoke

JWKS Endpoint

GET /oauth/jwks
{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "kid": "key-2024-01-15", "use": "sig", "alg": "EdDSA", "x": "11qYAYKxCrfVS_7TyWQHOg..." } ] } # Resource servers fetch this to # verify JWT signatures

Key Rotation Best Practices

6-12 mo

Regular rotation
schedule

Grace Period

Old key in JWKS
during transition

Audit Trail

All key operations
logged

Groups & Permissions

Group-Based Access Control

Organize users into groups with associated permissions for role-based access control (RBAC).

Create Group

POST /api/v1/groups
{ "name": "Engineering", "description": "Engineering team members", "permissions": [ "code:read", "code:write", "deploy:staging" ] }

Manage Membership

Group Membership APIs
# Add user to group POST /api/v1/groups/{group_id}/members { "user_id": "550e8400-e29b..." } # Remove user from group DELETE /api/v1/groups/{group_id}/members/{user_id} # Get group members GET /api/v1/groups/{group_id}/members

Default Groups

Administrators

Full system access with admin:* permissions. First user is automatically added.

Users

Basic user access with user:profile permission. All users added by default.

SCIM v2 Provisioning

SCIM User Operations

Create User

POST /scim/v2/Users
{ "schemas": [ "urn:ietf:params:scim:schemas:core:2.0:User" ], "userName": "john.doe@company.com", "name": { "givenName": "John", "familyName": "Doe" }, "emails": [ { "value": "john.doe@company.com", "primary": true } ], "active": true }

Update User

PATCH /scim/v2/Users/:id
{ "schemas": [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ], "Operations": [ { "op": "replace", "path": "active", "value": false } ] } # Other operations GET /scim/v2/Users # List users GET /scim/v2/Users/:id # Get user DELETE /scim/v2/Users/:id # Delete user

SCIM Group Operations

Group Management
# Create Group POST /scim/v2/Groups { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "Engineering", "members": [ { "value": "user-uuid-1" }, { "value": "user-uuid-2" } ] } # List Groups GET /scim/v2/Groups # Update Group Membership PATCH /scim/v2/Groups/:id { "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ { "op": "add", "path": "members", "value": [{"value": "new-user-uuid"}] } ] }

SCIM Nested Groups (RFC 7643)

ZenoAuth supports nested groups where groups can contain other groups as members, enabling complex organizational hierarchies with transitive membership.

Nested Group Structure
# Create Parent Group POST /scim/v2/Groups { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "Engineering" } # Create Child Group POST /scim/v2/Groups { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "Backend Team" } # Add Child Group as Member (Nested) PATCH /scim/v2/Groups/engineering-uuid { "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "add", "path": "members", "value": [{ "value": "backend-team-uuid", "type": "Group" }] }] } # GET Group Returns Mixed Members GET /scim/v2/Groups/engineering-uuid { "id": "engineering-uuid", "displayName": "Engineering", "members": [ { "value": "user-1", "type": "User", "display": "john.doe" }, { "value": "backend-team-uuid", "type": "Group", "display": "Backend Team" } ] }

Key Features

  • Transitive Membership: Users in nested groups inherit parent group memberships automatically
  • Circular Reference Prevention: Database-level constraints prevent Group A → Group B → Group A cycles
  • Depth Limit: Supports up to 10 levels of nesting for recursive membership resolution
  • Bidirectional Sync: Works for both inbound (receiving) and outbound (pushing) SCIM provisioning
  • RFC 7643 Compliant: Full compliance with SCIM 2.0 nested groups specification

Service Provider Configuration

SCIM clients can discover capabilities via the ServiceProviderConfig endpoint.

GET /scim/v2/ServiceProviderConfig
{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"], "patch": { "supported": true }, "bulk": { "supported": false }, "filter": { "supported": true, "maxResults": 200 }, "changePassword": { "supported": false }, "sort": { "supported": false }, "etag": { "supported": false }, "authenticationSchemes": [ { "type": "oauthbearertoken", "name": "OAuth Bearer Token", "description": "Authentication via OAuth 2.0 Bearer Token" } ] } # Resource Types Discovery GET /scim/v2/ResourceTypes

Admin Interface Setup

Next.js Admin Dashboard

ZenoAuth includes a modern Next.js 15 admin dashboard with React 19 and shadcn/ui components.

Development Setup

Local Development
# Navigate to admin directory cd zenoauth-admin # Install dependencies npm install # Set API URL export NEXT_PUBLIC_API_URL=http://localhost:3051 # Start development server npm run dev # Admin UI at http://localhost:3050

Features

Admin Pages
# Available admin pages: /users User management /clients OAuth client management /groups Group management /scopes Scope configuration /analytics Usage analytics /audit Audit logs /tokens Token management /sso-providers SSO configuration /settings System settings

Scope Management

Configure custom OAuth scopes with granular permissions.

Scope API
# Create custom scope POST /api/v1/scopes { "name": "user:profile:read", "description": "Read user profile information" } # List all scopes GET /api/v1/scopes # Validate scopes for an application POST /api/v1/scopes/validate { "scopes": ["openid", "profile", "email"] }

Key Capabilities

React 19

Latest React with
Server Components

TanStack

Query for data
fetching & caching

shadcn/ui

Professional
UI components

Integration Examples

JavaScript/Node.js

Node.js Example
// Express.js middleware const jwt = require('jsonwebtoken'); const jwksClient = require('jwks-rsa'); const client = jwksClient({ jwksUri: 'https://auth.yourdomain.com/.well-known/jwks.json' }); function getKey(header, callback) { client.getSigningKey(header.kid, (err, key) => { const signingKey = key.publicKey || key.rsaPublicKey; callback(null, signingKey); }); } function authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.sendStatus(401); } jwt.verify(token, getKey, { audience: 'your-client-id', issuer: 'https://auth.yourdomain.com', algorithms: ['EdDSA'] }, (err, user) => { if (err) return res.sendStatus(403); req.user = user; next(); }); } // Protected route app.get('/protected', authenticateToken, (req, res) => { res.json({ message: 'Access granted!', user: req.user }); });

Python/Flask

Python Example
# Flask integration import jwt import requests from functools import wraps from flask import Flask, request, jsonify app = Flask(__name__) def get_jwks(): response = requests.get( 'https://auth.yourdomain.com/.well-known/jwks.json' ) return response.json() def token_required(f): @wraps(f) def decorated(*args, **kwargs): token = request.headers.get('Authorization') if not token: return jsonify({'error': 'Token missing'}), 401 try: token = token.split(' ')[1] # Remove 'Bearer ' jwks = get_jwks() # Verify token with Ed25519 public key decoded = jwt.decode( token, key=jwks['keys'][0], algorithms=['EdDSA'], audience='your-client-id', issuer='https://auth.yourdomain.com' ) current_user = decoded except jwt.InvalidTokenError: return jsonify({'error': 'Token invalid'}), 401 return f(current_user, *args, **kwargs) return decorated @app.route('/protected') @token_required def protected(current_user): return jsonify({ 'message': 'Access granted!', 'user': current_user })