Auth / Anubis¶
Anubis is the identity module for L.Edge.R — an edge-native authentication service built on Cloudflare Workers, Hono, D1, and WebCrypto. Ships as a module inside the L.Edge.R monorepo for MVP, designed for extraction into a standalone company-wide IdP.
Stack¶
| Concern | Technology |
|---|---|
| Runtime | Cloudflare Workers (V8) |
| Framework | Hono |
| Database | D1 (SQLite, shared with backend/) |
| JWT | jose (HS256 → RS256 on extraction) |
| Password hashing | WebCrypto crypto.subtle PBKDF2-SHA256 |
| Language | TypeScript (strict) |
Architecture¶
Follows the same Clean/Hexagonal pattern as backend/:
anubis/src/
├── domain/ Pure entities and domain errors. No external deps.
├── application/
│ ├── ports/ Interfaces (contracts) for infrastructure.
│ └── use-cases/ Orchestrate domain logic via ports.
└── infrastructure/
├── db/ D1 repository implementations.
├── jwt/ jose-based token service.
├── password/ WebCrypto PBKDF2 password service.
├── http/ Hono routes and auth middleware.
└── container/ Dependency injection factory.
Zero coupling to accounting domain. Auth domain imports nothing from src/domain/ or src/application/ outside src/auth/.
API¶
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /auth/signup |
No | Create account + get JWT |
| POST | /auth/login |
No | Verify credentials + JWT |
| GET | /auth/me |
Bearer token | Return current user |
Response: { token: string, user: { id, email, name } }
Setup¶
1. Install¶
npm install
2. Set JWT Secret¶
wrangler secret put JWT_SECRET
# Generate with: openssl rand -base64 32
3. Apply Migration¶
# Local:
wrangler d1 migrations apply auth-db --local --env local
# Production:
wrangler d1 migrations apply auth-db --env prod
4. Run Locally¶
npm run dev
Domain¶
AuthUser Entity¶
id: string (UUID v4)
email: string (lowercase, trimmed)
name: string
passwordHash: string
createdAt: string
updatedAt: string
AuthErrors¶
InvalidCredentialsError— wrong email or password (same message, prevents email enumeration)UserAlreadyExistsError— signup with existing emailUnauthorizedError— missing or invalid token
Use Cases¶
SignupUseCase¶
- Normalize email
- Check for existing user →
UserAlreadyExistsErrorif found - Hash password (PBKDF2-SHA256, 310,000 iterations)
- Create
AuthUserwith UUID - Save to D1
- Sign JWT
- Return
{ token, user: { id, email, name } }
LoginUseCase¶
- Normalize email
- Find by email →
InvalidCredentialsErrorif not found - Verify password →
InvalidCredentialsErrorif false (same error as step 2 — no enumeration) - Sign JWT
- Return
{ token, user }
ValidateTokenUseCase¶
- Verify JWT
- Find user by
subclaim - Return
AuthUser
Security Details¶
Password Hashing (PBKDF2)¶
- Algorithm: PBKDF2-SHA256, 310,000 iterations (NIST SP 800-132)
- Stored format:
pbkdf2:sha256:<iterations>:<base64salt>:<base64hash> - Format allows iteration count upgrades without breaking existing hashes
JWT (HS256 → RS256)¶
- MVP: HS256 symmetric.
JWT_SECRETis the signing key. - On extraction: RS256 asymmetric. Auth Worker holds private key; consumer Workers verify via
GET /auth/jwks.json. - Expiry: 7 days for MVP (no refresh token yet)
jti(JWT ID) included for future token revocation
JWT claims:
{
"sub": "<user-id>",
"email": "[email protected]",
"name": "User Name",
"iat": 1700000000,
"exp": 1700604800,
"jti": "<uuid>"
}
Rate Limiting (MVP)¶
Cloudflare WAF rules (free tier):
- POST /auth/login — max 10 requests/IP/min → 429
- POST /auth/signup — max 5 requests/IP/min → 429
Key Design Decisions¶
HS256 now, RS256 on extraction¶
MVP uses symmetric HMAC-SHA256. backend/ validates tokens using the same JWT_SECRET. When this module extracts to a standalone Worker, switch to RS256: auth holds the private key, all consumer services verify via GET /auth/jwks.json without needing the secret.
Shared D1 database¶
Auth and backend/ bind to the same D1 instance, sharing the users table. Auth owns the password_hash column. On extraction, create a dedicated auth D1 and migrate user records.
Roadmap¶
Phase 2 — Hardening¶
- Refresh tokens
- Password reset
- Google OAuth
- Role-based authorization middleware
- Token revocation list (KV store, check
jti) - Re-hash passwords on login if iteration count outdated
Phase 3 — Extraction to Standalone Worker¶
- Move to own repo/Worker project
- Generate RSA key pair, switch to RS256
- Add
GET /auth/jwks.json - Add admin console UI
- Update
backend/authMiddleware to fetch JWKS remotely - Create dedicated D1, migrate users