Home / security / api-only-user-hardening
api-only-user-hardening
Provision and harden integration (API-only) users: no UI login, IP restrictions, minimum permission set, session lifetime, and monitoring. NOT for human admin account hardening — use admin/integration-user-management.
Open the skill folder on GitHub
API-Only User Hardening
An integration identity is the highest-value credential in most orgs. It is long-lived, it usually carries broader object access than any human, and its secret sits in a config store outside Salesforce's control. Hardening it is four decisions plus a monitoring commitment:
| Decision | Hardened choice |
|---|---|
| Licence | Salesforce Integration user license |
| Profile | Minimum Access - API Only Integrations — not a clone of anything |
| Scope | One permission set, named objects, named operations, viewAllRecords over ViewAllData |
| Authentication | OAuth 2.0 client credentials or JWT bearer — no password anywhere |
| Ongoing | Named owner, exported login history, alerting, rotation cadence |
The permission that makes the rest coherent:
"If a user has the API Only User permission, they can access Salesforce only via APIs, regardless of their other permissions." — Metadata API Developer Guide
"Regardless of their other permissions" makes this a mode switch, not a permission among many.
Before Starting
- Count the integrations against the org's licences. "One or more Salesforce
Integration user licenses are available by default in Enterprise, Unlimited, Performance, and Developer editions, with more add-on licenses available to purchase." A one-identity-per-integration design is a procurement item first.
- Enumerate the objects and operations this integration actually performs. Not
"needs access to Salesforce" — the object list and, per object, whether it reads, creates, edits, or deletes.
- Ask whether the caller has provisioned static egress, in both IPv4 and IPv6.
If not, IP restriction is unavailable and the posture must be carried elsewhere.
- Check whether anything renders UI as this identity before enabling API Only
User on an existing account — Visualforce, Lightning Out, Experience Cloud, a scheduled report. If something does, that is a second integration sharing the account.
Core Concepts
The licence carries a contractual boundary
"The Salesforce Integration API permission set license extends and restricts specific user and object permissions for system-to-system integrations. It may not be used for human users to access Salesforce data or features through any user interface."
A human using an integration licence is a licence-compliance problem as well as a security one. That boundary is a feature — it makes accidental reuse an event rather than a convenience.
The client credentials flow enforces API-only
<oauthConfig>
<isClientCredentialEnabled>true</isClientCredentialEnabled>
<oauthClientCredentialUser>etl.warehouse@example.com</oauthClientCredentialUser>
</oauthConfig>
"
oauthClientCredentialUser— The execution user for the OAuth 2.0 client credentials flow. Salesforce returns access tokens on behalf of this user. This user must have the API Only permission."
The platform will not let you bind a machine flow to a UI-capable identity. Both fields are API 56.0 and later.
Scope with viewAllRecords, not ViewAllData
<objectPermissions>
<object>Opportunity</object>
<allowRead>true</allowRead>
<viewAllRecords>true</viewAllRecords>
<allowCreate>false</allowCreate>
<allowEdit>false</allowEdit>
<allowDelete>false</allowDelete>
<modifyAllRecords>false</modifyAllRecords>
</objectPermissions>
Per-object, review-visible, and defensible line by line. Modify All Data grants read, create, edit, and delete on every object and bypasses all sharing.
The IP control is split across two screens
Profile loginIpRanges sets the restriction; the connected app's ipRelaxation decides whether that app honours it. ENFORCE (default) honours it; BYPASS "Allows a user to run this app without org IP restrictions." Audit both together or you are auditing neither.
Passwords bring three inherited problems
Expiry from a cloned profile (ProfileSessionSetting.passwordExpiration — 0, 30, 60, 90, 180, 365, where 0 never expires), the MFA requirement that applies to direct logins, and a copyable credential in a config store. Salesforce's own guidance: "For security, we recommend blocking user-agent and username-password flows."
Login History is not your system of record
"The Login History page shows up to 20,000 records of user logins for the past 6 months."
For a frequent integration that is days. Export on a schedule or stream to a SIEM, and decide this at provisioning — unretained data cannot be recovered.
Common Patterns
Pattern A — the hardened baseline
Salesforce Integration licence → Minimum Access - API Only Integrations → one scoped permission set → connected app with client credentials, ipRelaxation set to ENFORCE, and profile Login IP Ranges. Full metadata in