API keys without scope restrictions grant the same access as the owner's full session — a compromised key is equivalent to a compromised account. CWE-285 (Improper Authorization) and CWE-284 cover the failure to limit what an API key can do. OWASP A01 (Broken Access Control) flags credential over-privilege as a primary risk. NIST 800-53 AC-6 (Least Privilege) and IA-3 (Device Identification) require that credentials be limited to their intended purpose. A read-only integration that holds a full-permission key can delete all user data, initiate payment changes, or export sensitive records — damage that scoped keys would prevent.
High because a single compromised full-permission API key exposes the entire account, whereas a scoped key limits the blast radius to the permitted operations.
Add a scopes array to the API key model and enforce scope checks during request handling. The key is only valid for operations that match its declared scopes.
// prisma/schema.prisma
model ApiKey {
id String @id @default(cuid())
userId String
scopes String[] // e.g., ['read:posts', 'write:posts']
}
// lib/auth/api-key.ts
export async function validateApiKey(key: string, requiredScope: string) {
const apiKey = await db.apiKey.findUnique({ where: { key } });
if (!apiKey || !apiKey.scopes.includes(requiredScope)) return null;
return apiKey;
}
When generating keys, let users select which scopes to grant — this enforces least privilege at the point of creation rather than relying on callers to self-limit.
ID: saas-authorization.api-auth.api-keys-scoped
Severity: high
What to look for: Count all relevant instances and enumerate each. Look for an API key system — a api_keys or tokens table in the schema, an API key generation endpoint, or middleware that validates API keys from Authorization: Bearer headers or X-API-Key headers. Check whether the key model has a scopes or permissions field. Check whether the validation middleware verifies the key's scope against the requested action.
Pass criteria: API keys are created with specific scopes (e. At least 1 implementation must be verified.g., read:only, write:posts, admin) and those scopes are enforced when the key is used — a read-only key cannot perform write operations.
Fail criteria: API keys grant the same level of access as the owning user's full session, with no ability to restrict to specific operations. All API keys have the same unlimited permissions regardless of intended use.
Skip (N/A) when: No API key system detected — the application only supports session-based authentication with no programmatic API access.
Detail on fail: "API keys provide full account access without scope restrictions. A compromised API key grants complete access to the user's account." (Note where key validation occurs.)
Remediation: Add a scopes array to your API key model and verify the required scope during request handling.
// prisma/schema.prisma
model ApiKey {
id String @id @default(cuid())
userId String
scopes String[] // e.g., ['read:posts', 'write:posts']
// ...
}
// lib/auth/api-key.ts
export async function validateApiKey(key: string, requiredScope: string) {
const apiKey = await db.apiKey.findUnique({ where: { key } });
if (!apiKey || !apiKey.scopes.includes(requiredScope)) return null;
return apiKey;
}
When generating API keys, allow users to select which scopes to grant — this follows the principle of least privilege.