Authentication Architecture
Three-layer authentication system for humans, machines, and enterprises
Overviewβ
Synap uses a layered authentication strategy matching different use cases:
| Layer | Technology | Use Cases | Status |
|---|---|---|---|
| Layer 1: Human Users | Ory Kratos | Web UI login, sessions | β Operational |
| Layer 2: Machine Clients | API Keys + JWT | Intelligence Hub, n8n, integrations | β Operational |
| Layer 3: Enterprise OAuth2 | Ory Hydra | SSO, federation | π Planned |
Layers 1 & 2 are production-ready. Layer 3 (Hydra) is configured but not yet deployed.
Architectureβ
Layer 1: Human Users (Kratos)β
WHO: End users logging into the Synap web interface
STATUS: β
Fully operational
Purposeβ
Manage user identities with:
- Email/password authentication
- OAuth social login (Google, GitHub)
- Session management
- Self-service flows (registration, login, password reset)
Implementationβ
// packages/api/src/context.ts
export async function createContext(req: Request): Promise<Context> {
// DEV MODE: Bypass auth for testing
const testUserId = req.headers.get('x-test-user-id');
if (process.env.NODE_ENV === 'development' && testUserId) {
return { db, authenticated: true, userId: testUserId, ... };
}
// PRODUCTION: Validate Kratos session
const authModule = await import('@synap/auth');
const session = await authModule.getSession(req.headers);
if (session?.identity) {
return {
db,
authenticated: true,
userId: session.identity.id,
user: {
id: session.identity.id,
email: session.identity.traits.email,
name: session.identity.traits.name
},
session
};
}
return { db, authenticated: false };
}
Usageβ
Login Flow:
- User visits
/self-service/login(proxied from Kratos) - Kratos creates session and issues cookie
- Browser sends cookie with each API request
protectedProcedurevalidates session via Kratos
Docker Configuration:
# docker-compose.yml
kratos:
image: oryd/kratos:v1.3.0
profiles: ["auth"] # Enable with: docker compose --profile auth up
ports:
- "4433:4433" # Public API
- "4434:4434" # Admin API
Layer 2: Machine Clients (API Keys)β
WHO: Intelligence Hub, n8n, Zapier, external services
STATUS: β
Production-ready (351 tests passing)
Purposeβ
Long-lived credentials for automated systems with:
- bcrypt-hashed API keys
- Granular scope permissions
- Automatic rotation scheduling (90 days)
- Usage tracking and audit logs
- Rate limiting
Database Schemaβ
CREATE TABLE api_keys (
id UUID PRIMARY KEY,
user_id TEXT NOT NULL,
key_name TEXT NOT NULL, -- "n8n Production"
key_prefix TEXT NOT NULL, -- 'synap_user_' | 'synap_hub_live_' | 'synap_hub_test_'
key_hash TEXT NOT NULL, -- Bcrypt cost factor 12
scope TEXT[] NOT NULL, -- ['notes', 'tasks', 'webhook:manage']
is_active BOOLEAN DEFAULT TRUE,
expires_at TIMESTAMPTZ,
last_used_at TIMESTAMPTZ,
usage_count BIGINT DEFAULT 0,
rotated_from_id UUID,
rotation_scheduled_at TIMESTAMPTZ, -- Auto-suggests after 90 days
-- Audit trail
created_at TIMESTAMPTZ, created_by TEXT,
revoked_at TIMESTAMPTZ, revoked_by TEXT, revoked_reason TEXT
);
Usageβ
Create API Key:
// User authenticated via Kratos session
POST /trpc/apiKeys.create
{
"keyName": "n8n Production",
"scope": ["notes", "tasks", "entities"],
"expiresInDays": 90
}
// Response (key shown ONCE!)
{
"key": "synap_user_abc123xyz...",
"keyId": "uuid-...",
"message": "β οΈ Save this key securely. It will not be displayed again."
}
Use API Key Directly (e.g., n8n):
POST /api/n8n/entities
Authorization: Bearer synap_user_abc123xyz...
{
"type": "note",
"content": "Meeting notes..."
}
// Synap validates key and executes action
Hub Protocol Flow (two-step):
// Step 1: Hub requests short-lived token
POST /trpc/hub.generateAccessToken
Authorization: Bearer synap_hub_live_xyz... // Long-lived API key
{
"requestId": "req-123",
"scope": ["notes", "tasks"],
"expiresIn": 300 // 5 minutes
}
// Response: Short-lived JWT token
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": 1737360000000
}
// Step 2: Hub uses JWT for data access
POST /trpc/hub.requestData
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": ["notes"]
}
API keys prove identity (long-lived), JWT tokens are scoped and time-limited (security best practice).
Implementation Filesβ
- Service:
packages/api/src/services/api-keys.ts(352 lines) - Router:
packages/api/src/routers/api-keys.ts(176 lines) - Schema:
packages/database/src/schema/api-keys.ts(102 lines) - Tests:
packages/api/src/services/api-keys.test.ts(351 lines, β all passing)
Layer 3: Enterprise OAuth2 (Hydra)β
WHO: Enterprise OAuth2 clients, SSO federation
STATUS: π Planned (code exists but not deployed)
Purposeβ
Industry-standard OAuth2 for enterprise integrations:
- OAuth2 Client Credentials flow
- Authorization Code flow (PKCE)
- Token introspection
- Multi-tenant support
Current Stateβ
The code infrastructure exists but Ory Hydra is not running in production. Deploy when enterprise demand requires OAuth2 standard instead of API keys.
Code Location: packages/auth/src/ory-hydra.ts
// OAuth2 client management (ready but unused)
export async function createOAuth2Client(client: {
client_id: string;
client_secret: string;
grant_types: string[];
scope: string;
}) {
const { data } = await hydraAdmin.createOAuth2Client({
oAuth2Client: client
});
return data;
}
// Token introspection (ready but unused)
export async function introspectToken(token: string) {
const { data } = await hydraPublic.introspectOAuth2Token({ token });
return data.active ? data : null;
}
When to Deployβ
Deploy Ory Hydra when you need:
- β Enterprise SSO (SAML, OAuth2 federation)
- β Multi-tenant OAuth2 clients
- β Standards compliance (some enterprises require OAuth2 over API keys)
- β Token introspection for distributed systems
Estimated timeline: When >5 enterprise customers request it.
Comparison: When to Use Whatβ
| Use Case | Layer | Auth Method | Why |
|---|---|---|---|
| Web UI login | Layer 1 | Kratos session | Human-friendly, multi-device |
| Intelligence Hub | Layer 2 | API Key β JWT | Proven in Hub Protocol V1.0 |
| n8n integration | Layer 2 | API Key | Simple, n8n-native |
| Personal scripts | Layer 2 | API Key | Easy setup, long-lived |
| Enterprise SSO | Layer 3 | OAuth2 (Hydra) | Industry standard |
| Zapier (public app) | Layer 3 | OAuth2 (Hydra) | Zapier requires OAuth2 |
Security Best Practicesβ
For All Layersβ
- HTTPS Only - All auth in production must use TLS
- Rate Limiting - Prevent brute force attacks
- Audit Logging - Log all authentication events
- Secret Rotation - Regular key/token rotation
Layer-Specificβ
Kratos (Humans):
- β Session cookies: HttpOnly, Secure, SameSite
- β Password requirements: min 8 chars, complexity rules
- β Multi-factor authentication (roadmap)
API Keys (Machines):
- β bcrypt hashing (cost factor 12)
- β Automatic rotation scheduling (90 days)
- β Scope enforcement (read vs write)
- β Usage tracking and anomaly detection
Hydra (Enterprise):
- π Short-lived access tokens (15min)
- π Refresh token rotation
- π Client secret hashing
Migration Guideβ
From No Auth β Kratosβ
See Ory Setup Guide
From API Keys β OAuth2 (Future)β
When migrating to Hydra:
- Keep API keys working (backward compatibility)
- Add OAuth2 endpoint
- Dual middleware (accept both)
- Deprecate API keys over 6-12 months
Troubleshootingβ
"Session invalid" errorsβ
Cause: Kratos not running or session expired
Solution:
# Start Kratos
docker compose --profile auth up -d
# Check Kratos health
curl http://localhost:4433/health/ready
"API key validation failed"β
Cause: bcrypt comparison error or key revoked
Solution:
- Check
api_keystable:is_active = true - Verify key prefix matches pattern
- Check expiration:
expires_at > NOW()
"OAuth2 client not found" (Future)β
Cause: Hydra not deployed
Status: Expected - Hydra is planned, not operational
Next:
- Data Confidentiality - Privacy details
- Hub Protocol Flow - Plugin authentication flow