---
url: /salesforce-devops-starter-kit/blocks/sfdx-login.md
description: Authenticate to any org via SFDX Auth URL or the JWT flow.
---

# 🔐 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:

```bash
sf org display --target-org my-org --verbose --json > authFile.json
```

The 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:

```bash
cat authFile.json | base64
```

We 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](https://docs.github.com/en/actions/security-guides/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:

```yaml
# 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-org
```

The SF CLI in this example workflow is installed via the action [sfdx-cli-setup](https://github.com/svierk/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](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_jwt_flow.htm) | Salesforce DX Developer Guide
* [How To Use GitHub Actions, OAuth and SFDX-CLI for Continuous Integration](https://salesforcedevops.net/index.php/2022/04/05/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:

1. **client-id** | OAuth client ID (consumer key) of the custom connected app
2. **jwt-secret-key** | Contents of the server.key file containing the private key
3. **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:

```yaml
# 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](https://github.com/svierk/sfdx-cli-setup).

> **Note:** The `username` is passed as a [configuration variable](https://docs.github.com/en/actions/learn-github-actions/variables) (`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 from `secrets.*` 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 own `Successfully authorized …` line) to display `***` instead of the real username. If you intentionally want the username hidden, keep it in `secrets.*` 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](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-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. |

```yaml
- 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 `@main` or `@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](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot) for `github-actions`** in 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 a `permissions:` block granting only what the job needs (`contents: read` in most cases).
* **`persist-credentials: false` on 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"`) in `run:` 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`, never `pull_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:

* [org login sfdx-url](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_org_commands_unified.htm#cli_reference_org_login_sfdx-url_unified)
* [org login jwt](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_org_commands_unified.htm#cli_reference_org_login_jwt_unified)

## Releases

Latest release notes can be found on the [release page](https://github.com/svierk/sfdx-login/releases).

## License

The scripts and documentation in this project are released under the [MIT License](https://github.com/svierk/sfdx-login/blob/main/LICENSE).

***

➡️ **Full source & releases:** [svierk/sfdx-login](https://github.com/svierk/sfdx-login)
