๐ SFDX Login โ
This repository implements a simple GitHub composite action that allows logging into any Salesforce org from CI/CD automations based on either a Salesforce DX (SFDX) authorization URL or using a JSON web token (JWT). Logging into an org authorizes the CLI to run other commands that connect to that org, such as deploying or retrieving a project. You can log into different types of orgs, such as sandboxes, Dev Hubs, Env Hubs, production orgs, and scratch orgs.
Usage โ
Log in to a Salesforce org using a Salesforce DX authorization URL โ
To be able to log in with an SFDX Auth URL, you must first generate it. The easiest option to achieve this is to redirect the output of the following command for an already authorized org to a JSON file like:
sf org display --target-org my-org --verbose --json > authFile.jsonThe resulting JSON file contains the URL in the "sfdxAuthUrl" property of the "result" object. Since we need the authFile.json contents for the login action, but saving raw JSON inputs in GitHub secrets is known to cause problems, we perform an additional step and encode the contents as a Base64 string to avoid headaches like:
cat authFile.json | base64We then only have to store the Base64 string received in a GitHub action secret, e.g. SFDX_AUTH_URL, and can reference it whenever we are using the action in one of our workflows. A complete guide to secrets can be found here: Using secrets in GitHub Actions
In a GitHub workflow, the use of the action after the initial checkout step and the installation of the SF CLI could then look like this:
# Least-privilege token: this job only reads the repository.
permissions:
contents: read
jobs:
validation:
name: Validation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7.0.1
with:
persist-credentials: false # don't leave the GITHUB_TOKEN in .git/config for later steps
- name: Install SF CLI
uses: svierk/sfdx-cli-setup@v1.1.3
- name: Salesforce Org Login
uses: svierk/sfdx-login@v1.4.2
with:
sfdx-url: ${{ secrets.SFDX_AUTH_URL }}
alias: awesome-orgThe SF CLI in this example workflow is installed via the action sfdx-cli-setup.
Log in to a Salesforce org using a JSON web token (JWT) โ
The JWT login flow requires a custom connected app to be created as well as a digital certificate, also called a digital signature, to sign the JWT request. You can create a self-signed certificate using OpenSSL. How to achieve this is already well documented:
- Authorize an Org Using the JWT Flow | Salesforce DX Developer Guide
- How To Use GitHub Actions, OAuth and SFDX-CLI for Continuous Integration | Blog Post
The following three parameters must be passed to the login action:
- client-id | OAuth client ID (consumer key) of the custom connected app
- jwt-secret-key | Contents of the server.key file containing the private key
- username | Username of the user logging in
In a GitHub workflow, the use of the action after the initial checkout step and the installation of the SF CLI could then look like this:
# Least-privilege token: this job only reads the repository.
permissions:
contents: read
jobs:
validation:
name: Validation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7.0.1
with:
persist-credentials: false # don't leave the GITHUB_TOKEN in .git/config for later steps
- name: Install SF CLI
uses: svierk/sfdx-cli-setup@v1.1.3
- name: Salesforce Org Login
uses: svierk/sfdx-login@v1.4.2
with:
client-id: ${{ secrets.SFDX_CONSUMER_KEY }}
jwt-secret-key: ${{ secrets.SFDX_JWT_SECRET_KEY }}
username: ${{ vars.SFDX_USERNAME }}The SF CLI in this example workflow is installed via the action sfdx-cli-setup.
Note: The
usernameis passed as a configuration variable (vars.*) rather than a secret. A Salesforce username is not a credential on its own โ the actual secrets are the app's consumer key and the private key. GitHub automatically redacts any value that comes fromsecrets.*as***in all logs and in the job summary, so storing the username as a secret would cause the action output (and the SF CLI's ownSuccessfully authorized โฆline) to display***instead of the real username. If you intentionally want the username hidden, keep it insecrets.*and the***masking is expected behaviour.
Inputs โ
Provide either sfdx-url (SFDX Auth URL flow) or the client-id / jwt-secret-key / username trio (JWT flow).
| Name | Required | Default | Description |
|---|---|---|---|
sfdx-url | no | Base64 encoded SFDX authorization URL (uses the force:// protocol) for the SFDX Auth URL flow. | |
client-id | no | OAuth client ID (consumer key) of the connected app for the JWT login flow. | |
jwt-secret-key | no | Contents of the server.key private key file for the JWT login flow. | |
username | no | Username of the user logging in for the JWT login flow. | |
set-default-dev-hub | no | false | Set the authenticated org as the default Dev Hub. |
set-default | no | true | Set the authenticated org as the default that all org-related commands run against. |
alias | no | Alias for the org. | |
step-summary | no | true | Write a result section to the GitHub Actions job summary. Set to false to avoid collisions with a custom workflow summary. |
Outputs โ
After a successful login the action exposes details of the authenticated org so that follow-up steps can reference them:
| Name | Description |
|---|---|
username | Username of the authenticated org. |
org-id | ID of the authenticated org. |
instance-url | Instance URL of the authenticated org. |
- name: Salesforce Org Login
id: login
uses: svierk/sfdx-login@v1.4.2
with:
sfdx-url: ${{ secrets.SFDX_AUTH_URL }}
alias: awesome-org
# Outputs are consumed as environment variables instead of being interpolated
# into the script itself - "${{ ... }}" would be pasted into the shell verbatim.
- name: Show authenticated org
env:
ORG_USERNAME: ${{ steps.login.outputs.username }}
ORG_INSTANCE_URL: ${{ steps.login.outputs.instance-url }}
run: echo "Logged in as $ORG_USERNAME ($ORG_INSTANCE_URL)"Security โ
Credentials are passed to the underlying CLI commands via environment variables instead of being interpolated into the shell script, which avoids leaking them into the rendered command and prevents command injection.
The temporary authFile.json file created during the SFDX Auth URL flow is removed automatically when the step finishes, even if the login fails. For the JWT flow, the private key is written to a server.key file in the runner's temporary directory ($RUNNER_TEMP) with 600 permissions and is intentionally kept for the duration of the job. This is required because the Salesforce CLI stores the key's path in the auth record and re-reads it on every JWT token refresh โ for example when a later step runs sf org create scratch, deploys metadata, or deletes the org. Removing the key right after login would cause those steps to fail with ENOENT: no such file or directory ... server.key. The key never persists beyond the job: GitHub-hosted runners are destroyed afterward, and $RUNNER_TEMP is cleared between jobs on self-hosted runners.
Versioning โ
Every uses: reference in the examples above is pinned to an exact release tag, e.g. svierk/sfdx-login@v1.4.2. Do the same in your own pipelines:
- Never reference a mutable ref such as
@mainor@v1. It runs whatever code sits on that branch/tag at run time โ with access to your org credentials โ so a compromised or re-pointed ref would run unnoticed. - Good โ pin to an exact release tag (
@v1.4.2). Readable, concrete, and bumped through reviewed pull requests. - Strictest โ pin to a full-length commit SHA with the version as a trailing comment. A SHA can never be re-pointed by the publisher; the cost is readability. Worth it for actions from publishers you don't control.
- Enable Dependabot for
github-actionsin the repository that hosts your workflows so those pins are bumped for you instead of silently ageing.
Recommendations for the calling workflow โ
- Least-privilege
GITHUB_TOKENโ declare apermissions:block granting only what the job needs (contents: readin most cases). persist-credentials: falseon checkout โ the token is not written to.git/config, so later steps (SF CLI, third-party actions) cannot reuse it.- Reference secrets and step outputs as environment variables (
"$ORG_USERNAME") inrun:scripts, never by interpolating${{ ... }}into the script itself โ that would allow command injection and can leak values into the log. - Validate pull requests with
pull_request, neverpull_request_targetโ the latter runs with the base repository's secrets, which would let a fork execute its own code against your org.
References โ
The two authorisation options supported by this GitHub composite action can be found in the Salesforce CLI Command Reference here:
Releases โ
Latest release notes can be found on the release page.
License โ
The scripts and documentation in this project are released under the MIT License.
โก๏ธ Full source & releases: svierk/sfdx-login
