04.07 API Key Management
API access requires the API Extra activated, the master API toggle on, and a per-user API key generated. Each user has at most one active key; the key inherits all of that user's permissions and team memberships. Generate, rotate, and revoke keys from the user's profile or from User Management.
Requires: API Extra
The API key generation surface, the
api_keystable, and theapi_authenticate()function live in the API Extra atsimplerisk/extras/api/. The Extra is bundled with most SimpleRisk distributions but must be activated. Without it, the API endpoints undersimplerisk/api/v2/exist but reject all requests with no authentication path.
Why this matters
API keys are how scripts, partner integrations, mobile clients, and CI pipelines authenticate to SimpleRisk. Every interactive integration pattern (a Slack bot that posts new high-impact risks, a CI job that closes risks when a PR ships, a mobile app that lets executives approve mitigations from their phone) routes through API keys. Getting key management right is the foundation: keys that can't be rotated, can't be scoped, or aren't revoked when an integration retires become accumulating credential debt.
The honest scope to know up front: API keys in SimpleRisk are user-scoped and inherit all of that user's permissions and team memberships. There's no separate "API token with limited scope" concept; the key is functionally a long-lived credential for the user it belongs to. If alice has the submit_risks permission and is a member of the Engineering team, alice's API key can submit risks and see Engineering's records. The right pattern for integration accounts is to create a dedicated user (e.g., slack-bot) with exactly the permissions that integration needs, then generate the key for that user. Don't generate API keys against your interactive admin user.
The other thing worth knowing: one active key per user. Generating a new key invalidates the previous one (rotation rather than coexistence). For two parallel integrations needing API access, create two users (one per integration), each with its own key. There's no per-key naming or scope-tagging; the key is the user.
The third thing: keys don't expire. Core has no expiration field on the API key; once generated, it's valid until manually rotated or the user is disabled. For programs that require periodic rotation (PCI DSS, SOC 2 type 2 controls), the rotation discipline has to come from operational practice rather than from a per-key TTL.
Before you start
Have these in hand:
- Admin access to Configure → Settings (for the master API toggle) and to Configure → User Management (for per-user key administration on others' accounts; users can manage their own keys from My Account).
- A clear understanding of the integration's required permissions and team scope. API keys carry the user's full permission set and team memberships. Don't generate a key against an admin account and then try to "limit it to just risks" — there's no scope-limiting. Instead, design the integration user account with the right permissions from the start.
- A secure place to store the generated key. The key is shown once at generation time. Store it in a secrets manager (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, 1Password, etc.). Don't paste it into a chat thread, an email, or a config file checked into version control.
Step-by-step
1. Activate the API Extra
Sidebar: Configure → Extras → API Extra → Activate. The activation registers the API key management surface and the api_authenticate() function in the include chain.
If the Extra is already active (most production installs ship with it active by default), this step is a no-op.
2. Enable the master API toggle
Sidebar: Configure → Settings → API. The master toggle (api in the settings table — value 'true' or 'false') governs whether the API responds to requests at all. With the toggle off, the API endpoints return a 503-equivalent error regardless of whether a valid key is presented.
Enable the toggle. Save.
This is also where the API v1 toggle lives (separately) for installs that need to accept requests against the legacy /api/v1/ namespace; for new integrations, leave v1 off and use /api/v2/ exclusively.
3. Create a dedicated user for each integration
For each integration, create a SimpleRisk user account whose sole purpose is to hold the API key. The pattern:
- Configure → User Management → Add User.
- Username: descriptive (e.g.,
slack-bot,vendor-risk-feed,ci-deployment-tagger). The username appears in audit-trail entries for actions the integration takes; pick something that identifies the integration. - Email: a real email address you control (often a distribution list). The user record needs a valid email; integration users typically don't receive notifications, but the field is required.
- Password: generate a long random password and discard it (the integration won't use it). The login surface for the integration is the API key, not the password.
- Permissions: grant only what the integration needs. If it submits risks, grant
submit_risksand the team membership for the team the risks should be assigned to. If it reads compliance test data, grant the relevant view permissions. Don't grantadminor any permission the integration doesn't actively use. - Teams: assign the integration user to only the teams whose data the integration should see. Team membership filters what the integration can read just like it does for interactive users.
- Save the user.
The discipline pays off: when an integration is compromised or retired, you can disable just that user without affecting anyone else.
4. Generate the API key
The generation surface is on the user's profile (for self-service) or on the admin user-management page (for admin-managed integration users):
Admin path (recommended for integration users):
- Configure → User Management → [integration user].
- Find the API Key section.
- Click Generate API Key (or Rotate API Key if one already exists).
- The page displays the new key as a 64-character random string. Copy it now; once you leave the page, the plaintext is gone — only the hash is stored in
api_keys.
User self-service path:
- The user logs in.
- My Account → API Keys (or the equivalent label).
- Same generate / rotate flow.
The implementation: set_api_key() calls generate_token(64) to produce the random string, hashes it via generateHash() (with the per-install api_salt setting), and writes the hash to api_keys.api_key_hash along with user_id. The plaintext key is what you see; the hash is what's stored.
5. Store the key securely
Move the generated key into a secrets manager immediately. The patterns:
- Cloud-native deployments: AWS Secrets Manager, Azure Key Vault, GCP Secret Manager.
- On-prem or hybrid: HashiCorp Vault, CyberArk, or your existing secrets-management solution.
- Small-scale / dev: 1Password / Bitwarden, with the key never leaving an encrypted vault.
The integration code retrieves the key from the secrets manager at runtime; the key never lands in a config file, a CI variable, or a developer's clipboard for longer than the few seconds between generate-and-paste-into-vault.
6. Test the key
Verify the key authenticates correctly before considering the integration live:
curl -H "X-API-KEY:
" \ -H "Accept: application/json" \ https://your-simplerisk-instance.example.com/api/v2/risks
Expected: a JSON response listing the risks the integration user has access to (subject to team filter and permissions).
If the response is 401 Unauthorized or 403 Forbidden, check:
- Is the master
apitoggle on? - Is the API Extra active?
- Is the integration user enabled and not locked out?
- Did you copy the full 64-character key (not truncated)?
- Is the request hitting
/api/v2/(not/api/v1/or unversioned/api/)?
The API also accepts the key as a key URL parameter or POST body field, but the X-API-KEY header is the recommended pattern (URLs and POST bodies show up in logs more often than headers).
7. Rotate keys on a cadence
For programs with rotation requirements:
- Generate a new key on the same user (the Rotate API Key button is the same action as Generate API Key; it replaces the existing key with a new one and invalidates the old one immediately).
- Update the new key in the secrets manager.
- Trigger the integration to pick up the new key (whatever your integration's reload mechanism is).
- Verify the integration is still working with the new key.
Because rotation invalidates the old key immediately, a brief window of "old key dead, new key not yet picked up" is the typical gotcha. Coordinate the rotation with the integration's deployment cadence; for integrations that read the key from the secrets manager on every request, the rotation is near-instantaneous.
For programs without strict rotation requirements: rotate at user-offboarding events (the engineer who set up the integration leaves) and at incidents (a developer accidentally committed the key). Otherwise the rotation is largely ceremonial; the key is as secure as the storage of its hash and the storage of the plaintext in the secrets manager.
8. Revoke keys when integrations retire
When an integration is decommissioned:
- Disable the integration user (don't just remove the key — disable the user too, so any forgotten cached key can't be used). Configure → User Management → [user] → Disable.
- Revoke the API key explicitly (
Remove API Keyaction), which deletes theapi_keysrow for that user. - Audit-trail check: confirm no recent activity from the user account that would suggest the integration is still in use somewhere.
The disable-then-revoke order matters: disable first, so no race-condition request slips through during the revoke; revoke second, so the row is gone and there's no possibility of accidental re-enable bringing the key back.
9. Audit API activity
The API records activity in the standard audit trail (the audit_log table, surfaced in the operator UI). For the integration user, audit entries attribute actions to that user. Review periodically:
- Are the actions taken by the integration user expected?
- Are there login attempts from unexpected source IPs (suggesting key compromise)?
- Are there permission-denied attempts (suggesting the integration's permission set is wrong, or the integration is doing something unintended)?
For higher-volume integrations, the API also records request-level details if API logging is enabled (see The Cron Jobs for log rotation considerations).
10. Plan for "all my keys are gone"
A scenario worth rehearsing: the secrets manager is restored from backup and the latest keys aren't in it; or the integration user account is accidentally deleted; or a careless rotation invalidated keys before integrations were updated.
Recovery path:
- Generate a new key for the affected user.
- Update the secrets manager.
- Push the new key to the integration.
- Audit for any failed-auth alerts the integration emitted during the gap.
This is straightforward but smoother if you've done it once before. Test the recovery in a non-production environment.
Common pitfalls
A handful of patterns recur with API key management.
-
Generating keys against admin accounts. The admin's API key can do anything: submit risks, modify users, deactivate Extras, change settings. Compromise of that key compromises the entire SimpleRisk install. Always create a dedicated, least-privileged user for each integration.
-
Sharing keys across integrations. Two integrations sharing one key means rotating the key affects both; revoking the key in one breaks the other; audit-trail attribution is ambiguous. Create a user per integration.
-
Storing keys in source control. A 64-character random string in a
config.jsonchecked into Git is a credential leak waiting to happen. Use a secrets manager or environment variables sourced from one. Rungit secretsor equivalent scanning on the repo. -
Forgetting the master
apitoggle. Users generate keys, configure integrations, and then everything 503s because the master toggle is off. Verify the toggle is on as part of the integration setup checklist. -
Treating the lack of expiration as "keys are eternal." Operationally, integrations get retired, employees leave, and credentials should rotate. The lack of a built-in TTL is a tooling gap; don't treat it as an absence of need.
-
Mixing v1 and v2 API calls in integrations. If you generated the key against a user with v2-specific permissions, calling v1 endpoints might behave differently (or be rejected by the v1-disabled toggle). Use
/api/v2/exclusively for new integrations. -
Not revoking the key when an integration retires. A retired integration with a still-valid key is a silent risk. The key works; nothing alerts you that it's unused; if compromised, the activity could go unnoticed for months. Revoke at retirement.
-
Forgetting that the API key inherits team membership. A key for
alice(team: Engineering) submitting a risk from a CI job creates a risk attributed to alice on Engineering. If you intended the risk to be attributable to "the CI system" and visible to Operations, alice's key isn't right; create a CI user with the right team membership and permissions. -
Not rotating after key exposure. "I accidentally pasted the key into the wrong Slack channel — I'll just leave it." The key is now in someone's clipboard or Slack history forever; rotate immediately and consider what other systems might have logged the request.
-
Revealing the key after generation by reading the database. The database stores the hash, not the plaintext. Once you click away from the generation page, the plaintext is gone — there's no "show me the key again" path. Plan for this; if the key is lost, the only recovery is rotation.
Related
- Local Authentication and Password Policies
- Multi-Factor Authentication
- The Authentication Extra Overview
- Session Management and Timeout
- Managing Users, Teams, and Roles
- The Permission Model
- Installing Extras
Reference
- Permission required: Users can manage their own API keys (My Account → API Keys); admins can manage any user's keys via User Management.
- API endpoint(s):
POST /api/v2/users/{id}/apikey(regenerate);DELETE /api/v2/users/{id}/apikey(revoke). The API itself is consumed via the standard/api/v2/...endpoints with the key supplied asX-API-KEYheader (recommended),keyquery parameter, orkeyPOST body field. - Implementing files:
simplerisk/extras/api/index.php(api_authenticate(),set_api_key(),get_user_api_key(),remove_api_key());simplerisk/api/v2/index.php(the v2 API router);simplerisk/account/profile.php(user-self generation surface);simplerisk/admin/user_management.php(admin generation surface). - Database tables:
api_keys(user_idPK,api_key_hashVARCHAR(256)). config_settingskeys:api(master API toggle);api_salt(per-install salt for key hashing).- Key generation:
generate_token(64)produces a 64-character random plaintext token;generateHash($token)hashes it (crypt-based, usingapi_salt) before storage. The plaintext is shown once at generation; only the hash is stored. - External dependencies: None beyond the API Extra and a configured
api_salt.