ERP Authentication Comparison: OAuth Flows, Certificates & Service Accounts Across Major ERPs

Type: ERP Integration System: Salesforce, SAP S/4HANA, Oracle Fusion, Dynamics 365, NetSuite, Workday Confidence: 0.88 Sources: 8 Verified: 2026-03-02 Freshness: evolving

TL;DR

System Profile

This comparison covers the six most widely integrated cloud ERPs as of early 2026. Each system uses OAuth 2.0 as the primary API authentication mechanism, but the specific flows, token management, and certificate requirements differ substantially. On-premise deployments are excluded.

SystemRolePrimary AuthPreferred S2S Flow
SalesforceCRM + PlatformOAuth 2.0 (5 flows)JWT Bearer
SAP S/4HANA CloudERP CoreOAuth 2.0 + SAMLClient Credentials
Oracle Fusion Cloud ERPERP CoreOAuth 2.0 via IDCS/IAMAuthorization Code
Microsoft Dynamics 365ERP/CRMOAuth 2.0 via Entra IDClient Credentials + Certificate
NetSuiteERP/FinancialsOAuth 2.0 (migrating from TBA)Authorization Code + PKCE
WorkdayHCM/FinanceOAuth 2.0 + ISURefresh Token (non-expiring)

API Surfaces & Capabilities

SystemOAuth ProviderSupported Auth MethodsCertificate Auth?Mutual TLS?API Key Auth?
SalesforceBuilt-in OAuthOAuth 2.0 (5 flows), SAMLYes (JWT flow)NoNo
SAP S/4HANA CloudSAP BTP / Cloud IdentityOAuth 2.0, SAML 2.0, Client Cert, Basic (dev)Yes (mTLS)YesNo
Oracle Fusion CloudIDCS / OCI IAMOAuth 2.0, Basic (deprecated), SAMLYes (JWT assertion)NoNo
Dynamics 365Microsoft Entra IDOAuth 2.0, Certificate, Client SecretYesNoNo
NetSuiteBuilt-in OAuthOAuth 2.0, TBA (deprecated)Yes (M2M RSA)NoNo
WorkdayBuilt-in OAuthOAuth 2.0, ISU, SAMLYes (JWT bearer)NoNo

Rate Limits & Quotas

Authentication-Specific Limits

SystemToken Requests/HourConcurrent SessionsFailed Auth LockoutNotes
SalesforceNo explicit limit5 active tokens/user/app10 failed attemptsSession timeout configurable (default 2h)
SAP S/4HANA CloudThrottled per arrangementPer-tenant limitsConfigurableToken endpoint: 100 req/s
Oracle Fusion CloudIDCS: 50 req/min/clientLimited by IDCS tier5 attempts = 30-min lockRefresh token: 7-day default
Dynamics 365Entra ID: 10 req/s/tenantNo hard limitSmart lockoutAccess token: 60-90 min
NetSuiteNo explicit limit10 concurrent/integration6 failed attemptsOAuth 2.0 tokens: 60 min
WorkdayNo explicit limitPer-ISUConfigurableNon-expiring refresh tokens

Authentication

OAuth Flow Comparison by System

SystemS2S FlowUser-Context FlowToken LifetimeRefresh Token?Certificate Required?
SalesforceJWT BearerAuth Code (Web Server)Session timeout (2h default)No (JWT) / Yes (Auth Code)Yes (X.509)
SAP S/4HANA CloudClient CredentialsSAML Bearer Assertion3600s (1h)Yes (SAML flow)Optional (mTLS)
Oracle Fusion CloudAuth Code (via IDCS)Auth Code + PKCE3600s (1h)Yes (7-day)Optional (JWT assertion)
Dynamics 365Client Cred + Secret/CertAuth Code + PKCE60-90 minYes (until revoked)Recommended over secrets
NetSuiteOAuth 2.0 M2MAuth Code + PKCE3600s (1h)Yes (7-day default)Yes (RSA for M2M)
WorkdayRefresh Token (non-expiring)Auth CodeUntil revokedYes (non-expiring)Optional (JWT bearer)

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START -- Which ERP auth flow should I use?
|
+-- Server-to-Server (no user interaction)
|   +-- Salesforce --> JWT Bearer (certificate-based)
|   +-- SAP S/4HANA Cloud --> Client Credentials (via BTP)
|   +-- Oracle Fusion Cloud --> Auth Code (IDCS service account)
|   +-- Dynamics 365 --> Client Credentials + Certificate
|   +-- NetSuite --> OAuth 2.0 Machine-to-Machine (RSA cert)
|   +-- Workday --> Refresh Token with ISU (non-expiring)
|
+-- User-Context (delegated)
|   +-- Salesforce --> Authorization Code (Web Server)
|   +-- SAP S/4HANA Cloud --> SAML Bearer Assertion
|   +-- Oracle Fusion Cloud --> Authorization Code + PKCE
|   +-- Dynamics 365 --> Authorization Code + PKCE (Entra ID)
|   +-- NetSuite --> Authorization Code + PKCE
|   +-- Workday --> Authorization Code
|
+-- Machine-to-Machine (highest security)
|   +-- Salesforce --> JWT Bearer (cert signs every request)
|   +-- SAP S/4HANA Cloud --> mTLS (Client Certificate)
|   +-- Oracle Fusion Cloud --> JWT Client Assertion
|   +-- Dynamics 365 --> Certificate thumbprint auth
|   +-- NetSuite --> OAuth 2.0 M2M with RSA cert
|   +-- Workday --> JWT Bearer with registered cert
|
+-- Security requirement?
    +-- Standard --> OAuth Client Credentials or JWT
    +-- High --> Certificate-based or mTLS
    +-- Compliance --> Certificate + token rotation + audit

Quick Reference

CapabilitySalesforceSAP S/4HANAOracle FusionDynamics 365NetSuiteWorkday
OAuth 2.0Full (5 flows)FullFull (IDCS)Full (Entra ID)FullFull
Best S2SJWT BearerClient CredAuth Code (IDCS)Client Cred + CertM2M (RSA)Refresh Token
Certificate AuthX.509mTLS + Client CertJWT AssertionCert thumbprintRSAJWT Bearer cert
Token Lifetime2h (configurable)1h1h60-90 min1hConfigurable
Refresh TokenAuth Code onlySAML flow7-dayUntil revoked7-dayNon-expiring
Secret ExpiryNo limitNo limitConfigurable24-month maxNo limitNo limit
Identity ProviderBuilt-inSAP IAS/BTPIDCS/OCI IAMEntra IDBuilt-inBuilt-in
Scope ModelConnected AppComm. ArrangementIDCS App scopesAzure permissionsIntegration recordDomain policies
mTLS SupportNoYesNoNoNoNo
Legacy AuthUsername-PasswordBasic (dev)Basic (dep.)N/ATBA (dep. 2027.1)ISU password
Setup ComplexityModerateHighHighLow-ModerateModerateHigh

Step-by-Step Integration Guide

1. Salesforce: JWT Bearer Authentication

Create a connected app, upload X.509 certificate, configure integration user. [src1, src6]

# Generate self-signed certificate (dev/testing only)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout salesforce_private.pem -out salesforce_cert.pem \
  -subj "/CN=SalesforceIntegration/O=YourOrg"

# Exchange JWT for access token
curl -X POST https://login.salesforce.com/services/oauth2/token \
  -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  -d "assertion=YOUR_SIGNED_JWT"

Verify: Response contains access_token and instance_url.

2. SAP S/4HANA Cloud: Client Credentials

Set up Communication Arrangement with OAuth 2.0 in SAP BTP. [src2, src7]

curl -X POST "https://{tenant}.authentication.{region}.hana.ondemand.com/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=CID&client_secret=CSEC"

Verify: 200 OK with JSON containing access_token and expires_in.

3. Dynamics 365: Certificate-Based S2S

Register app in Entra ID, upload certificate, create application user. [src3]

# Test with WhoAmI after obtaining token
curl -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "OData-Version: 4.0" \
  "https://YOUR_ORG.crm.dynamics.com/api/data/v9.2/WhoAmI"

Verify: JSON response with UserId and OrganizationId.

4. NetSuite: OAuth 2.0 Machine-to-Machine

Create integration record, generate RSA certificate, configure M2M flow. [src4]

# Generate RSA key pair
openssl genrsa -out netsuite_private.pem 4096
openssl rsa -in netsuite_private.pem -pubout -out netsuite_public.pem

Verify: 200 OK with access_token and token_type: Bearer. Token valid 60 minutes.

Code Examples

Python: Multi-ERP Authentication Factory

# Input:  ERP system name, credentials dict
# Output: Authenticated session with valid access token

import requests, jwt, time
from msal import ConfidentialClientApplication

class ERPAuthFactory:
    @staticmethod
    def salesforce_jwt(client_id, username, private_key_path,
                       login_url="https://login.salesforce.com"):
        with open(private_key_path, 'r') as f:
            private_key = f.read()
        payload = {'iss': client_id, 'sub': username,
                   'aud': login_url, 'exp': int(time.time()) + 300}
        assertion = jwt.encode(payload, private_key, algorithm='RS256')
        resp = requests.post(f"{login_url}/services/oauth2/token",
            data={'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                  'assertion': assertion})
        resp.raise_for_status()
        return resp.json()

    @staticmethod
    def dynamics365_certificate(client_id, tenant_id, private_key_path,
                                 thumbprint, resource_url):
        with open(private_key_path, 'r') as f:
            private_key = f.read()
        app = ConfidentialClientApplication(client_id,
            authority=f"https://login.microsoftonline.com/{tenant_id}",
            client_credential={'private_key': private_key,
                               'thumbprint': thumbprint})
        return app.acquire_token_for_client(
            scopes=[f"{resource_url}/.default"])

cURL: Quick Token Test for Each ERP

# --- Salesforce (Username-Password -- testing only) ---
curl -X POST https://login.salesforce.com/services/oauth2/token \
  -d "grant_type=password&client_id=CID&client_secret=CSEC&username=USER&password=PASS"

# --- SAP (Client Credentials) ---
curl -X POST "https://TENANT.authentication.eu10.hana.ondemand.com/oauth/token" \
  -d "grant_type=client_credentials&client_id=CID&client_secret=CSEC"

# --- Dynamics 365 (Client Secret -- testing only) ---
curl -X POST "https://login.microsoftonline.com/TENANT/oauth2/v2.0/token" \
  -d "grant_type=client_credentials&client_id=CID&client_secret=SEC&scope=https://org.crm.dynamics.com/.default"

# --- Workday (Refresh Token) ---
curl -X POST "https://DOMAIN.workday.com/ccx/oauth2/TENANT/token" \
  -d "grant_type=refresh_token&client_id=CID&client_secret=CSEC&refresh_token=RT"

Data Mapping

Authentication Credential Mapping

ConceptSalesforceSAP S/4HANAOracle FusionDynamics 365NetSuiteWorkday
Client IDConnected App Consumer KeyOAuth Client ID (BTP)IDCS Client IDEntra ID App IDIntegration Consumer KeyAPI Client ID
Client SecretConnected App Consumer SecretOAuth Client Secret (BTP)IDCS Client SecretEntra ID Client SecretIntegration Consumer SecretAPI Client Secret
Service AccountIntegration User + Connected AppCommunication UserIDCS Confidential AppApplication User (Entra ID)Integration RecordISU
Token Endpoint/services/oauth2/token/oauth/token (BTP UAA)/oauth2/v1/token (IDCS)/oauth2/v2.0/token/services/rest/auth/oauth2/v1/token/ccx/oauth2/{tenant}/token

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

ErrorSystem(s)MeaningResolution
invalid_grantAllToken request rejectedRegenerate JWT assertion or refresh token
invalid_clientAllClient credentials rejectedVerify credentials; check secret expiration
INVALID_SESSION_IDSalesforceAccess token expiredGenerate new JWT and request new token
AADSTS700027Dynamics 365Certificate validation failedUpload correct cert; check thumbprint
AADSTS7000215Dynamics 365Invalid client secretGenerate new secret or use certificate
USER_EXCEPTIONNetSuiteAuth failedCheck integration record; migrate to OAuth 2.0
401 - invalid_tokenWorkdayToken expired or ISU deactivatedRequest new token; verify ISU is active

Failure Points in Production

Anti-Patterns

Wrong: Hardcoding client secrets in source code

# BAD -- secrets in source code get committed to Git
client_secret = "dJk8s!kLm9@pQrSt"
token = get_token(client_id, client_secret)

Correct: Use a secrets manager or environment variables

# GOOD -- secrets retrieved from vault at runtime
import os
client_secret = os.environ["D365_CLIENT_SECRET"]  # Or use Key Vault

Wrong: Single service account across all integrations

# BAD -- one ISU for all Workday integrations; termination breaks everything
config = {"isu": "global_integration_user", "password": "shared"}

Correct: Dedicated service accounts per integration

# GOOD -- separate ISU per integration; failures are isolated
integrations = {
    "payroll": {"isu": "ISU_PAYROLL", "client_id": "payroll_client"},
    "hcm": {"isu": "ISU_HCM", "client_id": "hcm_client"}
}

Wrong: Caching tokens without expiry checks

# BAD -- cached token used without checking validity
cached_token = redis.get("sf_token")
response = requests.get(url, headers={"Authorization": f"Bearer {cached_token}"})

Correct: Token refresh with expiry awareness

# GOOD -- check expiry before using cached token
def get_valid_token(cache, auth_func, buffer_seconds=300):
    cached = cache.get("token_data")
    if cached and cached["expires_at"] > time.time() + buffer_seconds:
        return cached["access_token"]
    token_data = auth_func()
    token_data["expires_at"] = time.time() + token_data.get("expires_in", 3600)
    cache.set("token_data", token_data)
    return token_data["access_token"]

Common Pitfalls

Diagnostic Commands

# Salesforce: Verify token and connected app
curl -H "Authorization: Bearer TOKEN" \
  https://INSTANCE.my.salesforce.com/services/oauth2/userinfo

# SAP S/4HANA Cloud: Test OAuth token
curl -H "Authorization: Bearer TOKEN" \
  "https://HOST/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?\$top=1"

# Oracle Fusion Cloud: Introspect token
curl -X POST "https://IDCS_HOST/oauth2/v1/introspect" \
  -u "CLIENT_ID:CLIENT_SECRET" -d "token=TOKEN"

# Dynamics 365: Test with WhoAmI
curl -H "Authorization: Bearer TOKEN" -H "OData-Version: 4.0" \
  "https://ORG.crm.dynamics.com/api/data/v9.2/WhoAmI"

# NetSuite: Verify OAuth 2.0 token
curl -H "Authorization: Bearer TOKEN" \
  "https://ACCOUNT.suitetalk.api.netsuite.com/services/rest/record/v1/customer?limit=1"

# Workday: Test ISU + OAuth
curl -H "Authorization: Bearer TOKEN" \
  "https://DOMAIN.workday.com/ccx/api/v1/TENANT/workers?limit=1"

Version History & Compatibility

SystemAuth ChangeDateImpactMigration Notes
SalesforceMFA enforcement2024-02Username-Password breaksMigrate to JWT Bearer
SAP S/4HANA CloudComm. Arrangement OAuth GA2023Standard auth methodNo migration for new integrations
Oracle FusionBasic Auth deprecated2025New integrations must use OAuthExisting basic auth still works
Oracle FusionIDCS to OCI IAM migration2025-2026Token endpoints changeUse discovery document
Dynamics 365ADAL sunset2022-06Must use MSALUpdate client libraries
NetSuiteTBA deprecation2027.1No new TBA integrationsMigrate to OAuth 2.0 M2M
WorkdayOAuth 2.0 M2M support2024JWT Bearer flow availableRecommended for new

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Evaluating OAuth flows across multiple ERPsNeed auth details for one ERP onlySystem-specific API capability card
Planning multi-ERP integration platformNeed rate limit or data model detailsERP rate limits comparison card
Migrating from basic auth to OAuth 2.0Need SSO/SAML for end-user loginSSO/SAML comparison card
Designing certificate management strategyNeed specific code for one systemSystem-specific integration guide

Cross-System Comparison

CapabilitySalesforceSAP S/4HANAOracle FusionDynamics 365NetSuiteWorkday
OAuth 2.0Full (5 flows)FullFull (IDCS)Full (Entra ID)FullFull
Best S2SJWT BearerClient CredAuth Code (IDCS)Client Cred + CertM2M (RSA)Refresh Token
Certificate AuthX.509mTLSJWT AssertionCert thumbprintRSAJWT Bearer cert
Token Lifetime2h (configurable)1h1h60-90 min1hConfigurable
Refresh TokenAuth Code onlySAML flow7-dayUntil revoked7-dayNon-expiring
mTLSNoYesNoNoNoNo
Legacy AuthUsername-PasswordBasic (dev)Basic (dep.)N/ATBA (dep. 2027.1)ISU password
Setup ComplexityModerateHighHighLow-ModerateModerateHigh
DocumentationExcellentGood (complex)Good (scattered)ExcellentGoodModerate

Important Caveats

Related Units