Skip to main content

Authentication Architecture

Three-layer authentication system for humans, machines, and enterprises


Overview​

Synap uses a layered authentication strategy matching different use cases:

LayerTechnologyUse CasesStatus
Layer 1: Human UsersOry KratosWeb UI login, sessionsβœ… Operational
Layer 2: Machine ClientsAPI Keys + JWTIntelligence Hub, n8n, integrationsβœ… Operational
Layer 3: Enterprise OAuth2Ory HydraSSO, federationπŸ”„ Planned
Current State

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:

  1. User visits /self-service/login (proxied from Kratos)
  2. Kratos creates session and issues cookie
  3. Browser sends cookie with each API request
  4. protectedProcedure validates 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"]
}
Why Two Steps?

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​

Not Yet Deployed

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 CaseLayerAuth MethodWhy
Web UI loginLayer 1Kratos sessionHuman-friendly, multi-device
Intelligence HubLayer 2API Key β†’ JWTProven in Hub Protocol V1.0
n8n integrationLayer 2API KeySimple, n8n-native
Personal scriptsLayer 2API KeyEasy setup, long-lived
Enterprise SSOLayer 3OAuth2 (Hydra)Industry standard
Zapier (public app)Layer 3OAuth2 (Hydra)Zapier requires OAuth2

Security Best Practices​

For All Layers​

  1. HTTPS Only - All auth in production must use TLS
  2. Rate Limiting - Prevent brute force attacks
  3. Audit Logging - Log all authentication events
  4. 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:

  1. Keep API keys working (backward compatibility)
  2. Add OAuth2 endpoint
  3. Dual middleware (accept both)
  4. 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_keys table: 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: