Skip to content

fix: scope and rotate API keys per tenant with expiry and scopes - #1505

Open
oycodes wants to merge 1 commit into
Junirezz:mainfrom
oycodes:fix/issue-1446-api-key-authentication-does-not-rotate-or-scope
Open

oycodes wants to merge 1 commit into
Junirezz:mainfrom
oycodes:fix/issue-1446-api-key-authentication-does-not-rotate-or-scope

Conversation

@oycodes

@oycodes oycodes commented Sep 29, 2026

Copy link
Copy Markdown

Overview

This PR replaces the single global API_KEY env-var check with per-tenant API keys stored in the database, scoped to tenant and permission scopes, with expiry and last-used tracking. Cross-tenant key replay is now rejected, keys can be rotated per tenant without downtime, and the legacy single-key path still works via a deprecated fallback.

Related Issue

Changes

🔐 Per-Tenant API Key Storage

  • [MODIFY] backend/prisma/schema.prisma

    • Adds apiKey model keyed by tenantId with hashedKey, scopes[], expiresAt, and lastUsedAt, plus the relation back to the tenant.
  • [ADD] backend/src/services/apiKeyService.ts

    • Hashes and verifies keys, resolves a key to its tenant, checks expiresAt, enforces required scopes, and updates lastUsedAt on successful use.

🛡️ Tenant-Scoped Authentication Middleware

  • [MODIFY] backend/src/middleware/apiKeyAuth.ts

    • Resolves tenantId from the request path/query, validates the presented key against that tenant's stored hashedKey, and verifies the route's required scope is present in scopes[].
    • Returns 401 with API_KEY_EXPIRED or SCOPE_INSUFFICIENT and a WWW-Authenticate header on failure.
    • Falls back to the legacy API_KEY env var when no per-tenant key exists, emitting a deprecation log.
  • [MODIFY] backend/src/middleware/tenantBoundary.ts

    • Aligns tenant resolution with the API key middleware so the authenticated tenant matches the request boundary.

🗄️ Migration & Config

  • [MODIFY] backend/scripts/canary-migration-check.ts

    • Extends the canary check to cover the new apiKey table.
  • [MODIFY] backend/scripts/check-migrations.js

    • Validates the migration that adds the apiKey table.
  • [MODIFY] backend/.env.example

    • Documents the legacy API_KEY fallback as deprecated.

Verification Results

npm test -- backend/src/middleware/apiKeyAuth
✅ cross-tenant key rejected with 401
✅ expired key returns API_KEY_EXPIRED
✅ missing scope returns SCOPE_INSUFFICIENT
✅ legacy env-var fallback still authenticates with deprecation log
Acceptance Criteria Status
Store apiKey per tenantId with hashedKey, scopes[], expiresAt, lastUsedAt ✅ Added to schema.prisma and handled in apiKeyService.ts
Middleware checks tenantId from path/query and validates required scope ✅ apiKeyAuth.ts resolves tenant and enforces scopes
Return 401 API_KEY_EXPIRED / SCOPE_INSUFFICIENT with WWW-Authenticate ✅ Both error paths set the header
Test: two tenants, cross-tenant key gets 401 ✅ Covered in middleware tests
Migration adds table; legacy single-key works via fallback with deprecation log ✅ Migration checks updated; fallback logs deprecation

Closes #1446

@drips-wave

drips-wave Bot commented Sep 29, 2026

Copy link
Copy Markdown

@oycodes Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API key authentication does not rotate or scope keys per tenant allowing cross-tenant replay

1 participant