Support OAuth 2.0 Client Credentials Flow for non-interactive Salesforce authentication
Feature request
Thank you for maintaining salesforcer. We rely on this package for production R scripts that connect to Salesforce, and it has been very useful.
Salesforce is moving away from username/password-based API authentication patterns, and we are trying to migrate our R automation to a non-interactive OAuth-based flow.
In Python, we use the simple_salesforce package and were able to update our scripts away from username/password/security-token authentication:
from simple_salesforce import Salesforce
sf = Salesforce(
username="...",
password="...",
security_token="...",
domain="..."
)
to connected-app based authentication using:
from simple_salesforce import Salesforce
sf = Salesforce(
consumer_key="...",
consumer_secret="...",
domain="..."
)
This lets scheduled jobs and server-side automation authenticate without storing Salesforce passwords or relying on browser-based OAuth.
Current limitation in salesforcer
From what I can tell, salesforcer::sf_auth() currently supports:
- Username/password/security-token authentication.
- Browser/cached OAuth 2.0 using
consumer_key, consumer_secret, and callback_url.
- Reusing a previously saved
Token2.0 object or .rds token file.
The package accepts consumer_key and consumer_secret, but those appear to be used for the existing interactive/cached OAuth flow via httr::oauth2.0_token(), not for a fully non-interactive server-to-server flow such as OAuth 2.0 Client Credentials.
For automated scripts, especially scheduled production jobs, the browser/cached-token approach is difficult to operationalize. The old username/password/security-token approach is also something we are trying to remove due to Salesforce authentication changes and security best practices.
Requested functionality
Would you consider adding first-class support for OAuth 2.0 Client Credentials Flow?
A possible interface could look like:
salesforcer::sf_auth(
auth_type = "client_credentials",
consumer_key = Sys.getenv("SALESFORCE_CONSUMER_KEY"),
consumer_secret = Sys.getenv("SALESFORCE_CONSUMER_SECRET"),
login_url = "https://login.salesforce.com"
)
or perhaps:
salesforcer::sf_auth_client_credentials(
consumer_key = Sys.getenv("SALESFORCE_CONSUMER_KEY"),
consumer_secret = Sys.getenv("SALESFORCE_CONSUMER_SECRET"),
login_url = "https://login.salesforce.com"
)
Internally, this would call:
POST /services/oauth2/token
with a form body like:
grant_type=client_credentials
client_id=<consumer_key>
client_secret=<consumer_secret>
and then store the returned access_token and instance_url in the package auth state so existing functions like sf_query(), Bulk API helpers, REST calls, etc. continue to work.
Why this matters
This would help teams migrate off username/password/security-token auth while still using salesforcer for production R jobs.
The main use case is:
- non-interactive scheduled scripts
- no browser login
- no Salesforce password stored in scripts or environment variables
- connected-app based authentication using
consumer_key, consumer_secret, and domain / login_url
- compatibility with Salesforce orgs enforcing newer authentication and security requirements
Possible workaround today
It looks like a workaround may be to manually request an OAuth access token in R, construct an httr::Token2.0 object, and then pass that token object directly to salesforcer::sf_auth(token = ...).
That seems preferable to directly modifying salesforcer's internal state, since sf_auth() already supports receiving a token object.
Conceptually, something like this may work:
sf_auth_client_credentials <- function(
consumer_key = Sys.getenv("SALESFORCE_CONSUMER_KEY"),
consumer_secret = Sys.getenv("SALESFORCE_CONSUMER_SECRET"),
domain = Sys.getenv("SALESFORCE_DOMAIN")
) {
login_url <- paste0("https://", domain)
token_url <- paste0(login_url, "/services/oauth2/token")
response <- httr::POST(
url = token_url,
body = list(
grant_type = "client_credentials",
client_id = consumer_key,
client_secret = consumer_secret
),
encode = "form"
)
httr::stop_for_status(response)
credentials <- httr::content(
response,
as = "parsed",
type = "application/json"
)
# `salesforcer::sf_auth_check()` expects OAuth tokens to have an `issued_at`
# value when checking whether a token may need refresh.
credentials$issued_at <- as.character(as.numeric(Sys.time()) * 1000)
endpoint <- httr::oauth_endpoint(
request = NULL,
base_url = paste0(login_url, "/services/oauth2"),
authorize = "authorize",
access = "token",
revoke = "revoke"
)
app <- httr::oauth_app(
appname = "salesforce",
key = consumer_key,
secret = consumer_secret
)
token_object <- httr::Token2.0$new(
endpoint = endpoint,
app = app,
credentials = credentials
)
salesforcer::sf_auth(token = token_object)
invisible(token_object)
}
Then a script could do:
sf_auth_client_credentials()
salesforcer::sf_query("SELECT Id, Name FROM Account LIMIT 10")
The concern with this workaround is that client-credentials tokens generally do not behave like normal browser OAuth tokens with refresh tokens. salesforcer may attempt to refresh OAuth tokens after they expire, so long-running jobs might need to request a new client-credentials token instead of calling the normal token refresh path.
Because of that, it would be helpful if salesforcer had native support for this flow and handled token acquisition and re-authentication in a package-supported way.
Questions
- Is support for OAuth 2.0 Client Credentials Flow already planned?
- If not, would you be open to adding it?
- Is the
Token2.0 workaround above compatible with how salesforcer expects OAuth tokens to behave?
- Is there a preferred approach for contributing this via PR?
Thanks again for maintaining the package.
Support OAuth 2.0 Client Credentials Flow for non-interactive Salesforce authentication
Feature request
Thank you for maintaining
salesforcer. We rely on this package for production R scripts that connect to Salesforce, and it has been very useful.Salesforce is moving away from username/password-based API authentication patterns, and we are trying to migrate our R automation to a non-interactive OAuth-based flow.
In Python, we use the
simple_salesforcepackage and were able to update our scripts away from username/password/security-token authentication:to connected-app based authentication using:
This lets scheduled jobs and server-side automation authenticate without storing Salesforce passwords or relying on browser-based OAuth.
Current limitation in
salesforcerFrom what I can tell,
salesforcer::sf_auth()currently supports:consumer_key,consumer_secret, andcallback_url.Token2.0object or.rdstoken file.The package accepts
consumer_keyandconsumer_secret, but those appear to be used for the existing interactive/cached OAuth flow viahttr::oauth2.0_token(), not for a fully non-interactive server-to-server flow such as OAuth 2.0 Client Credentials.For automated scripts, especially scheduled production jobs, the browser/cached-token approach is difficult to operationalize. The old username/password/security-token approach is also something we are trying to remove due to Salesforce authentication changes and security best practices.
Requested functionality
Would you consider adding first-class support for OAuth 2.0 Client Credentials Flow?
A possible interface could look like:
or perhaps:
Internally, this would call:
with a form body like:
and then store the returned
access_tokenandinstance_urlin the package auth state so existing functions likesf_query(), Bulk API helpers, REST calls, etc. continue to work.Why this matters
This would help teams migrate off username/password/security-token auth while still using
salesforcerfor production R jobs.The main use case is:
consumer_key,consumer_secret, anddomain/login_urlPossible workaround today
It looks like a workaround may be to manually request an OAuth access token in R, construct an
httr::Token2.0object, and then pass that token object directly tosalesforcer::sf_auth(token = ...).That seems preferable to directly modifying
salesforcer's internal state, sincesf_auth()already supports receiving a token object.Conceptually, something like this may work:
Then a script could do:
The concern with this workaround is that client-credentials tokens generally do not behave like normal browser OAuth tokens with refresh tokens.
salesforcermay attempt to refresh OAuth tokens after they expire, so long-running jobs might need to request a new client-credentials token instead of calling the normal token refresh path.Because of that, it would be helpful if
salesforcerhad native support for this flow and handled token acquisition and re-authentication in a package-supported way.Questions
Token2.0workaround above compatible with howsalesforcerexpects OAuth tokens to behave?Thanks again for maintaining the package.