Skip to content

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-key and x-api-secret headers
  • [ ] 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:
    1. Load RSA private key from Doppler
    2. Build JWT with claims: iss (client_id), sub (username), aud (login_url)
    3. Sign JWT with RSA-SHA256
    4. POST to Salesforce token endpoint
    5. 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 configuration

Implementation Phases

Phase 1: Inbound Auth (Day 1-2)

  1. Create auth plugin skeleton
  2. Implement API key + secret validation
  3. Build permission checking
  4. Create consumer storage in Redis
  5. Write unit tests

Phase 2: Key Rotation (Day 2-3)

  1. Implement rotation endpoint
  2. Build grace window logic
  3. Add credential expiry handling
  4. Write unit tests

Phase 3: Outbound Auth (Day 3-5)

  1. Implement JWT generation
  2. Build Salesforce token exchange
  3. Create token cache in Redis
  4. Implement token refresh worker
  5. Write unit tests

Phase 4: Doppler Integration (Day 5-6)

  1. Implement credential loading from Doppler
  2. Build credential refresh without restart
  3. Add health checks
  4. Write integration tests

Phase 5: Polish (Day 6-7)

  1. Add comprehensive logging
  2. Add auth metrics
  3. Run all tests
  4. Fix lint and type errors
  5. Update README.md

Dependencies

DependencyVersionPurpose
@fastify/jwt^9.0.0JWT verification
@fastify/secure-session^7.0.0Session management
ioredis^5.4.0Redis client
jose^5.4.0JWT/JWE/JWS library
node-forge^1.3.0RSA key handling
@doppler/sdk^1.0.0Doppler 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
  },
};

Marchay Platform Documentation