Multi-Strategy Authentication
smo1 advanced 10 min read
ELI5
purr-api accepts identity proof in five different formats, like a nightclub that accepts a passport, a driver’s licence, a membership card, a VIP wristband, or a text from the owner. The bouncer (auth middleware) checks each format in order until one works. If none work and the door requires ID, you are turned away. If the door is optional-ID, you can enter as a guest.
Technical Deep Dive
Auth Strategy Priority
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#e8f4f8', 'primaryTextColor': '#2d3748', 'primaryBorderColor': '#90cdf4', 'lineColor': '#718096', 'secondaryColor': '#f0fff4', 'tertiaryColor': '#fefcbf'}}}%%flowchart TD A[Incoming Request] --> B{Header:<br/>X-API-Key ?} B -->|Yes| C[Strategy 1:<br/>API Key] B -->|No| D{Header:<br/>Authorization: Bearer ?} D -->|Yes| E[Strategy 2:<br/>JWT Bearer] D -->|No| F{Cookie:<br/>airlock JWT ?} F -->|Yes| G[Strategy 3:<br/>Airlock RS256 JWT] F -->|No| H{Cookie:<br/>smo1_session ?} H -->|Yes| I[Strategy 4:<br/>Session Cookie] H -->|No| J{Header:<br/>X-Internal-Key ?} J -->|Yes| K[Strategy 5:<br/>Internal Key] J -->|No| L[No auth context]
C --> M{Valid ?} E --> M G --> M I --> M K --> M M -->|Yes| N[Set user context<br/>proceed to handler] M -->|No| O{OptionalAuth ?} O -->|Yes| L O -->|No| P[Return 401]Strategy Details
| # | Strategy | Identifier | Verification | Use case |
|---|---|---|---|---|
| 1 | API Key | X-API-Key: sk_... | SHA-256 hash lookup in DB; check revocation/expiration | Third-party integrations, scripts |
| 2 | JWT Bearer | Authorization: Bearer <jwt> | HMAC-SHA256 with JWTSecret; extract sub claim | Machine-to-machine, mobile apps |
| 3 | Airlock JWT | Cookie from airlock OIDC | RS256 verification against JWKS endpoint; validate issuer | Web dashboard (meow-web) |
| 4 | Session Cookie | smo1_session or __Secure-smo1.session_token | Forward to meow-web /api/auth/session; verify HMAC | Legacy BetterAuth compatibility |
| 5 | Internal API Key | X-Internal-Key | Simple string match against env var | Service-to-service (meow-web → purr-api) |
API Key Strategy
- Extract header
X-API-Key - Compute SHA-256 hash of the provided key
- Query
api_keystable:SELECT * FROM api_keys WHERE key_hash = $1 AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > NOW()) - If found, set user context from
user_id - Async: update
last_used_attimestamp (non-blocking)
Security note: The plaintext key is shown exactly once at creation. Only the hash is stored.
JWT Bearer Strategy
- Extract
Authorization: Bearer <token> - Parse JWT without verification first (to read
algheader) - Verify signature with
JWTSecretusing HMAC-SHA256 - Validate
expclaim - Extract
subclaim as user ID - Lookup user in PostgreSQL
Airlock JWT Strategy (RS256)
- Read airlock JWT from cookie (name varies by environment)
- Fetch JWKS from
airlock.devarno.cloud(cached for 5 minutes) - Verify signature with Ed25519/RS256 public key from JWKS
- Validate
iss,aud,expclaims - Extract
sub(user ID),email,name - Auto-create user if not exists in PostgreSQL (idempotent)
Session Cookie Strategy
- Read
smo1_sessionor__Secure-smo1.session_tokencookie - Forward to meow-web
GET /api/auth/sessionwith the cookie - meow-web verifies HMAC-SHA256 signature and expiry internally
- Returns user data; purr-api trusts this response
- Fallback: check for BetterAuth legacy session
Internal API Key
- Read
X-Internal-Keyheader - Compare against
INTERNAL_API_KEYenvironment variable - If match, set a synthetic “internal service” user context
- No user-specific permissions — full internal access
OptionalAuth vs. RequireAuth
| Middleware | Behaviour | Example routes |
|---|---|---|
| OptionalAuth | Extracts user context if present; allows anonymous | Public link info, landing page data |
| RequireAuth | Returns 401 if no valid auth | Dashboard, link CRUD, billing |
Rate Limiting Context
Auth middleware sets the rate-limit identifier before the rate limiter runs:
- Authenticated requests:
user:{user_id} - API key requests:
apikey:{api_key_id} - Anonymous requests:
ip:{ip_address}
This ensures authenticated users get their tier’s higher limit, while anonymous requests share a lower per-IP cap.
Key Terms
- JWKS → JSON Web Key Set; a JSON document containing public keys for JWT verification, fetched from the OIDC provider
- RS256 → RSA-SHA256 asymmetric signing; private key signs, public key verifies (used by Airlock)
- HMAC-SHA256 → Symmetric signing; same secret signs and verifies (used by internal JWT and session cookies)
- Auto-create user → On first Airlock login, purr-api creates a User row automatically to avoid manual onboarding
- Key prefix → First 8 characters of an API key plaintext, stored for UI display (
sk_live_...)
Q&A
Q: Why five strategies instead of just one? A: Different clients have different capabilities. Browsers use cookies. Scripts use API keys. Mobile apps may use JWT. Internal services use a shared secret. Supporting all five minimizes integration friction.
Q: What prevents someone from forging an API key?
A: Only the SHA-256 hash is stored. To forge a key, an attacker must find a preimage for a known hash (computationally infeasible for SHA-256). Key generation uses crypto/rand for 32+ bytes of entropy.
Q: Why does the Airlock strategy auto-create users? A: Airlock is the canonical identity provider. If Airlock says a user exists, purr-api trusts that and provisions a local record. This eliminates manual user registration and keeps identity state in one place.
Q: Can a request pass multiple auth checks simultaneously? A: Yes, but only the first valid strategy is used. If a request has both an API key and a session cookie, the API key wins (higher priority in the chain).
Examples
Think of the API as a high-security building with multiple entrances:
- API Key is a keycard issued to contractors — it opens specific doors, can be revoked remotely, and logs every use
- JWT Bearer is a temporary QR code sent to your phone — it expires after a set time and cannot be reused if stolen
- Airlock JWT is the main employee ID badge — it is issued by the central HR system (Airlock) and works at all company buildings
- Session Cookie is a guest pass — the front desk (meow-web) vouches for you, and the security guard (purr-api) trusts the front desk
- Internal Key is the master key held by building maintenance — it overrides everything but is never given to outsiders
neighbors on the map
- Link Protection implementing password-protected links
- Tier-Based Rate Limiting debugging 429 errors for specific users