Skip to main content

API Key Security

How ReplayCI generates, stores, and validates API keys and authentication credentials.


API keys

Generation

API keys are generated using cryptographically secure randomness:

Format: rci_live_<32-character-base64url>
Entropy: 192 bits
Example: rci_live_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6

The rci_live_ prefix enables quick identification and SecurityGate scanning.

Storage

API keys are never stored in plaintext. The raw key is:

  1. Shown to the user exactly once (at signup or key creation)
  2. Immediately hashed with SHA-256
  3. Stored as key_hash in the ApiKey database table

Subsequent authentication compares the SHA-256 hash of the presented key against the stored hash. The raw key cannot be recovered from the database.

Validation

Every request to /api/v1/* endpoints goes through API key authentication in middleware:

  1. Extract Bearer token from Authorization header
  2. Validate format matches the rci_live_ prefix
  3. Compute SHA-256 hash and look up against stored hashes
  4. Verify the key is not revoked
  5. Resolve the associated tenant and user for downstream authorization

Limits

ConstraintValue
Max active keys per tenant5
Key entropy192 bits
RevocationImmediate (next request rejected)
last_used_at trackingUpdated fire-and-forget on each request

Password hashing

User passwords are hashed using scrypt with industry-standard parameters:

  • Unique random salt per password
  • Constant-time comparison to prevent timing attacks
  • Passwords are never stored or logged in plaintext

Session tokens

Dashboard authentication uses signed session tokens:

PropertyValue
SigningHMAC-SHA256
Access token lifetime15 minutes
Refresh token lifetime7 days
Cookie flagsHttpOnly, SameSite=Strict, Secure (production)

Token refresh

When an access token expires, middleware automatically checks the refresh token. If valid, a new access token is issued transparently — no user interaction required. Refresh tokens are stored as SHA-256 hashes in the database; the raw token only exists in the browser cookie.

Token revocation

Refresh tokens can be revoked immediately. The token cleanup job removes expired and revoked tokens after their retention period (30 days for refresh tokens).


Rate limiting

Authentication endpoints have specific rate limits to prevent brute-force attacks:

EndpointLimitWindow
POST /api/auth/signup5 requestsPer hour (per IP)
POST /api/auth/forgot-password3 requestsPer hour (per email)
POST /api/v1/runs100 runsPer day (per tenant)
POST /* (general)30 requestsPer minute (per tenant)
GET /* (general)120 requestsPer minute (per tenant)

Rate limiting uses a token bucket algorithm to prevent brute-force and abuse.


Password reset

Password reset uses a secure token flow:

  1. User requests reset → system generates 32-byte random token
  2. SHA-256 hash stored in PasswordResetToken table
  3. Raw token sent via email link (HTTPS only)
  4. Token is single-useused_at timestamp marks consumption
  5. Token expires after 1 hour
  6. Existing unused tokens are invalidated when a new one is created

The forgot-password endpoint always returns 200 OK regardless of whether the email exists (anti-enumeration).


Email verification

New accounts must verify their email before using the API:

  1. Signup triggers a verification email (fire-and-forget — signup succeeds even if email fails)
  2. Verification token: 32-byte random, SHA-256 hashed in database, 24-hour TTL
  3. Unverified accounts receive 403 EMAIL_NOT_VERIFIED on /api/v1/* endpoints
  4. Dashboard access is not gated — users can view their dashboard while unverified
  5. Resend endpoint rate-limited to 3/hour

Secret redaction

The structured logger automatically redacts sensitive fields (API keys, tokens, credentials) at all nesting levels. Even in debug or error logs, credentials are never written in plaintext.


What we recommend

  1. Rotate API keys periodically — revoke old keys from Settings, create new ones
  2. Use environment variables — never put REPLAYCI_API_KEY in config files or source control
  3. Use strong passwords — scrypt is resistant to brute force, but a strong password is your first line of defense
  4. Check your CI secrets — ensure REPLAYCI_API_KEY and REPLAYCI_PROVIDER_KEY are stored in your CI platform's secret manager, not in workflow files