Appearance
Authentication System Build Brief
Build brief for the second epic of the API Gateway: Authentication System. This builds on the completed Gateway Foundation.
Overview
Epic: Authentication System Estimated effort: ~1-2 weeks Depends on: Gateway Foundation ✓ Enables: Rate Limiting, Connectors, Production readiness
Implement a two-layer auth system: inbound consumer auth (API key + secret, optional mTLS) and outbound vendor auth (OAuth 2.0 JWT Bearer for Salesforce). Credentials are stored in Doppler and rotated automatically.
Acceptance Criteria
1. Inbound Consumer Authentication
- [ ] Implement API key + secret validation middleware
- [ ] Extract credentials from
x-api-keyandx-api-secretheaders - [ ] Validate credentials against stored consumer records
- [ ] Return 401 for invalid credentials
- [ ] Return 403 for valid credentials but insufficient permissions
- [ ] Log every auth attempt with: consumer_id, endpoint, result, timestamp
2. Consumer Credential Management
- [ ] Define consumer schema:typescript
interface Consumer { id: string; name: string; apiKey: string; apiSecret: string; // hashed permissions: Permission[]; createdAt: Date; updatedAt: Date; rotatedAt: Date | null; expiresAt: Date | null; } interface Permission { resource: string; // 'members', 'quotes', 'bookings' actions: ('read' | 'write' | 'delete')[]; } - [ ] Implement credential storage in Redis (hash:
consumer:{api_key}) - [ ] Build credential lookup by API key
- [ ] Implement permission checking per resource/action
3. Key Rotation with Grace Window
- [ ] Implement rotation endpoint:
POST /v1/admin/consumers/:id/rotate - [ ] Generate new API key + secret pair
- [ ] Keep old credentials active for grace window (configurable, default 24h)
- [ ] Mark old credentials as rotated with expiry timestamp
- [ ] Reject old credentials after grace window expires
4. Outbound Vendor Authentication (Salesforce)
- [ ] Implement OAuth 2.0 JWT Bearer flow:
- Load RSA private key from Doppler
- Build JWT with claims:
iss(client_id),sub(username),aud(login_url) - Sign JWT with RSA-SHA256
- POST to Salesforce token endpoint
- Parse access_token from response
- [ ] Implement token storage in Redis:
Key: sf_token:{environment} Value: { access_token, instance_url, expires_at } TTL: expires_at - 5 minutes - [ ] Build per-environment configuration (dev, staging, prod)
5. Token Refresh Worker
- [ ] Implement proactive token refresh:
- Check token expiry every minute
- Refresh when <5 minutes remaining
- Use exponential backoff on refresh failure
- [ ] Build refresh queue in Redis for coordinating across replicas
- [ ] Implement token acquisition lock (prevent thundering herd)
6. JWT Token Cache
- [ ] Implement Redis cache for Salesforce tokens
- [ ] Build cache read on every outbound request
- [ ] Implement cache invalidation on token refresh
- [ ] Add cache hit/miss metrics
7. Doppler Integration
- [ ] Load consumer credentials from Doppler on startup
- [ ] Load Salesforce credentials from Doppler on startup
- [ ] Implement credential refresh without restart
- [ ] Add Doppler connection health check
8. Tests
- [ ] Unit tests for API key validation
- [ ] Unit tests for permission checking
- [ ] Unit tests for key rotation
- [ ] Unit tests for JWT generation
- [ ] Unit tests for token caching
- [ ] Unit tests for token refresh
- [ ] Integration test: valid credentials return 200
- [ ] Integration test: invalid credentials return 401
- [ ] Integration test: insufficient permissions return 403
- [ ] Integration test: rotated credentials work during grace window
- [ ] Integration test: expired rotated credentials return 401
File Structure (created by this brief)
gateway/src/
├── plugins/
│ ├── auth.ts # Main auth plugin (middleware)
│ └── __tests__/
│ └── auth.test.ts
├── auth/
│ ├── __init__.py
│ ├── inbound/
│ │ ├── __init__.py
│ │ ├── api-key.ts # API key + secret validation
│ │ ├── permissions.ts # Permission checking
│ │ └── rotation.ts # Key rotation logic
│ ├── outbound/
│ │ ├── __init__.py
│ │ ├── salesforce.ts # OAuth 2.0 JWT Bearer
│ │ └── token-cache.ts # Redis token cache
│ └── types.ts # Auth-related types
├── routes/
│ └── admin/
│ ├── consumers.ts # Consumer management endpoints
│ └── __tests__/
│ └── consumers.test.ts
└── config/
└── auth.ts # Auth configurationImplementation Phases
Phase 1: Inbound Auth (Day 1-2)
- Create auth plugin skeleton
- Implement API key + secret validation
- Build permission checking
- Create consumer storage in Redis
- Write unit tests
Phase 2: Key Rotation (Day 2-3)
- Implement rotation endpoint
- Build grace window logic
- Add credential expiry handling
- Write unit tests
Phase 3: Outbound Auth (Day 3-5)
- Implement JWT generation
- Build Salesforce token exchange
- Create token cache in Redis
- Implement token refresh worker
- Write unit tests
Phase 4: Doppler Integration (Day 5-6)
- Implement credential loading from Doppler
- Build credential refresh without restart
- Add health checks
- Write integration tests
Phase 5: Polish (Day 6-7)
- Add comprehensive logging
- Add auth metrics
- Run all tests
- Fix lint and type errors
- Update README.md
Dependencies
| Dependency | Version | Purpose |
|---|---|---|
| @fastify/jwt | ^9.0.0 | JWT verification |
| @fastify/secure-session | ^7.0.0 | Session management |
| ioredis | ^5.4.0 | Redis client |
| jose | ^5.4.0 | JWT/JWE/JWS library |
| node-forge | ^1.3.0 | RSA key handling |
| @doppler/sdk | ^1.0.0 | Doppler client |
Configuration
typescript
// config/auth.ts
export const authConfig = {
inbound: {
graceWindowHours: 24,
headerApiKey: 'x-api-key',
headerApiSecret: 'x-api-secret',
},
outbound: {
salesforce: {
tokenEndpoint: process.env.SALESFORCE_LOGIN_URL,
clientId: process.env.SALESFORCE_CLIENT_ID,
username: process.env.SALESFORCE_USERNAME,
keySecretRef: process.env.SALESFORCE_PRIVATE_KEY_SECRET_REF,
},
},
cache: {
tokenTtlSeconds: 300, // 5 minutes before expiry
refreshIntervalMs: 60000, // check every minute
},
};