Appearance
Authentication
Two distinct auth directions, each at our boundary.
1. Consumer → Gateway (Inbound)
Consumers authenticate to us. The spike used a simple API key + secret in headers (x-api-key, x-api-secret), validated by Fastify middleware.
Requirements
- Per-consumer credentials — Atlantes gets one key pair, future consumers get their own
- Scoped permissions — a consumer authorized for reads is not necessarily authorized for writes
- Rotation — old key valid for a grace window after rotation
- Keys in Doppler — never in consumer env files in production
- mTLS option — for high-trust consumers, mTLS is preferred over API key
Auth Middleware
POST /v1/quotes
x-api-key: `<consumer_key>`
x-api-secret: `<consumer_secret>`Returns 401 if invalid. Returns 403 if valid but not authorized for the requested resource. Logs every auth attempt with consumer ID, endpoint, and result.
2. Gateway → Our Resources (Outbound)
We authenticate to our own resources. For Salesforce, the spike validated OAuth 2.0 JWT Bearer flow — the same pattern applies to other resources that support it.
How It Works (Salesforce)
- The gateway holds a private key (RSA, in Doppler)
- On startup, the gateway builds a signed JWT asserting its identity
- The gateway POSTs the JWT to our SF token endpoint
- SF returns an access token
- Access token is cached in Redis (shared across gateway replicas)
- Token is refreshed proactively ~5 minutes before expiry
What the Spike Validated
- JWT generation with a self-signed cert
- Token exchange via
jsforce.Connection.getToken()with the options-object form - Live authenticated SOQL query against our Salesforce org
Requirements
- Private key in Doppler, never on disk in production
- Key rotation support (multiple active keys during rotation)
- Token refresh worker — proactive, not reactive
- Per-environment credentials (separate connected apps for dev, staging, prod)
3. Webhooks (Separate)
Webhook authentication is its own concern. See Webhooks.