Authenticating with Salesforce CLI in a remote system

Cláudio Saramagaio
10 min readJul 28, 2023

--

It’s simple to authenticate Salesforce environments using Salesforce CLI through one of the following commands, depending on whether you are using sf or sfdx commands.

sf org login web [options]

OR

sfdx auth:web:login [options]

In essence, when executing the aforementioned instructions, it simply opens a new browser tab with the appropriate login page for the intended environment type based on the options specified in the command. Then, you login through the browser, and that’s it!

But when it comes to systems which need to automatically authenticate without human interaction, such as server side applications, that need to interact with Salesforce environments using CLI, or even CI/CD pipelines, it may become a bit more challenging. However, there are some options to connect Salesforce environments using Salesforce CLI and predefined credentials. Thus, in the next section we are going through some examples.

The next sections will cover the following topics:
1. Stored Authorization URL
1.1. Security Concerns
2. JWT Flow Implementation
2.1. Create Self-signed certificate
2.2. Create Connected App
2.3. Authorize using JWT
2.4. Security Concerns
2.5. Video — Authorising Salesforce Org using JWT Flow (+ GitHub Actions implementation)
3. Resources (code examples)
4. References

1. Stored Authorization URL

The first way we’ll outline employs the web login method to locally authenticate the Org in order to export the local connection information and import it into the remote system.

Therefore, to join the organisation locally, we will first use one of the commands provided at the article’s beginning. Additionally, you may accomplish it inside of an SFDX project using the Salesforce Extension Pack for Visual Studio Code, as demonstrated below.

SFDX — Authorize Org through web browser

After logging in, you will be asked to allow Salesforce CLI to access your Org. At that point you just need select the “Allow” option, and that’s it. You are connected.

In the next step, you need to run the following command in the terminal to export the connection details:

sfdx force:org:display --verbose --json > authfile.json

Then open the generated file (authfile.json) and copy the value of the “sfdxAuthUrl” attribute.

SFDX — Export authorization URL

The value of sfdxAuthUrl should be stored within a text file on the remote system; for this example, we are going to name it “sfdxAuthUrl.txt”, and execute the following command:

sfdx auth:sfdxurl:store -f sfdxAuthUrl.txt -s

And it’s done! Your remote system is connected to Salesforce and ready to start using all the capabilities of the Salesforce CLI.

1.1. Security Concerns

It seems to have been very straightforward until now, but wait, what about the security? Is it safe to store this information anywhere on the remote system?

Well, to ensure security, we cannot simply store a file containing the authorization url, facing the risk that anyone who can get access to the file, whether intentionally or accidentally, automatically gains access to the Salesforce org, which can be a Production org. We must properly store this URL in a safe place because it’s very common to have systems and applications that connect to multiple Salesforce environments, and most of the people who work with them might not have access to all Salesforce environments. This is a truth, especially when talking about CI/CD tools, where it’s acceptable to have development teams that can access lower environments but cannot access Production environments, while the DevOps team and CI/CD tool should have access to all environments, including Production. Thus, we cannot simply store the authorization URL in the source repository.

The concern mentioned above can be solved by storing the information in environment variables, regardless of the system or technology. Most, if not all, CI/CD tools, containers to run applications, or even virtual servers have the possibility to safely store environment variables, which may require a special, or more restricted, type of access. On some specific platforms, like GitHub Actions, we can even have different types of variables with different security rules, as you can see in the example below.

GitHub Variables

GitHub provides us with two ways of storing environment variables. Under the Variables tab, we can find the normal environment variables that can be seen and edited, while under the “Secrets” tab, we can only read the variable name without having access to its content. Under this tab, we can change the variable value, but we cannot see its current value after saving.

GitHub was presented as an example, but other platforms and applications have similar features, like Bitbucket or GitLab, as presented in the following image. And, again, I presented these CI/CD tools, but it could be other types of containers to run applications.

Bitbucket and GitLab Variables

Therefore, what we need to do is safely store the authorization URL in a safe place and use it only when it’s needed — during the authentication phase. Since the command we presented before accepts a file as input, we create a temporary file, fill it out with the sfdxAuthUrl coming from an environment variable, use the file to execute the authentication command from Salesforce CLI, and then delete it. When talking about CI/CD pipelines, we don’t need to worry about that because everything that is not committed and pushed to the remote repository will be discarded.

echo "${{ secrets.SFDX_AUTH_URL }}" > sfdxAuthUrl.txt
sfdx auth:sfdxurl:store -f sfdxAuthUrl.txt -s

We can also do it using the new sf commands instead of sfdx, and it accepts the entire JSON file exported before. If we executed the same action using the corresponding sf command, it would look like:

sf org login sfdx-url --sfdx-url-file sfdxAuthUrl.txt
SFDX — Authorize Org using authorization URL

2. JWT Flow Implementation

JWT is an acronym for JSON Web Token, and it’s the preferred, or at least recommended, way when talking about connecting servers between them, even though it requires more steps such as creating a Connecting App and generating a certificate.

As mentioned before, to implement JWT Flow, you need a Connected App and a certificate, which is your digital signature, that will be uploaded to that Connected App. It is possible to use an existing private key and digital certificate or generate a new private key and self-signed certificate using OpenSSL. Thus, in the next sections, we are going to show you how to generate the certificate using OpenSSL, create the Connected App, and authorise the Org using JWT.

2.1. Create Self-signed Certificate

This first step is all about to generate our RSA keys and certificate. Basically RSA keys are composed by a public key and a private key, and everything that is encrypted by a the public key can only be decrypted by the respective private key and vice-versa. Regarding the certificate, it’s a trusted document containing the public key and information about the private key owner. [If you want to learn more about public/private keys and certificate check this article: Public/Private keys vs Certificates.] To perform the next actions you will need OpenSSL.

The first two commands geneate the private key and store it in file (server.key in this case). The server.pass.key will not be needed anymore.

> openssl genpkey -des3 -algorithm RSA -pass pass:[your password] -out server.pass.key -pkeyopt rsa_keygen_bits:2048

> openssl rsa -passin pass:[you password] -in server.pass.key -out server.key

The next two commands generate the certificate. The first command generates the certificate signing request in a .csr file, and the second command generates the final certificate (.crt), the one we are going to update later in the Connected App.

> openssl req -new -key server.key -out server.csr

> openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt

2.2. Create Connected App

The second step aims to create the Connected App we will use to connect the Org. So, initially, we need to setup the Connected App details. For that, in Setup, we navigate to App > App Manager and click “New Connected App” at the top right corner. It opens the page we show in the picture “Connected App Creation”, where you need to populate the mandatory fields. Here, we also need to choose the option “Enable OAuth Settings” and then “Use Digital Signatures”, where we need to upload the certificate we’ve generated (server.crt). Regarding the OAuth Scopes, we need to select the following options:
— Manage user data via APIs (api);
— Manage user data via Web browsers (web);
— Perform requests at any time (refresh_token, offline_access).

Connected App Creation

Once the Connected App created, it’s time to save the Consumer Key and Consumer Secret by clicking “Manage Consumer Details” as displayed in No. 1 in the picture “Connected App Management”. When accessing these details it opens a page with the details, as in the picture “Connected App — Consumer Details”.

Then, going back to the Connected App detail page, it’s time to configure the application access policies by clicking the “Manage” button.

Connected App Management
Connected App — Consumer Details

There are some steps to perform on this page. The first part regards access policies like OAuht and Session. And the second part allows you to pre-authorise profiles, and permission permission sets that will be granted to connect the Org using this Connected App.

Thus, after clicking the “Edit Policies” button (button no. 1 in the “Connected App — Policies” picture), you must change the “Permitted Users” to “Admin approved users are pre-authorised” because it’s mandatory for JWT. Then we can change some other configurations related to token and session expiration dates and times, respectively, as shown in the picture “Connected App — Edit Policies”.

Connected App — Policies
Connected App — Edit Policies

Back to the previous page, we can opt to pre-authorise groups of profiles or permission sets that are allowed to connect to the Org. For that, we can click either on “Manage Profiles” or “Manage Permission Sets”, and select the ones we want to provide access to, as shown in the picture “Connected App — Authorize Profile”.

Connected App — Authorize Profile

2.3. Authorize using JWT

The last step consists of executing the appropriate command to authorise the Org using JWT. In this step, we will need to use the consumer key from the Connected App. However, unlike other authentication types, we are not going to use the consumer secret; instead, we use the private key because we uploaded the certificate in the Connected App, so the private key will authenticate towards its certificate.


sf org login jwt
--client-id [conumer key from connected app]
--jwt-key-file [path to private key file (*.key)]
--username [username] --alias [alias]
--instance-url https://[your instance].my.salesforce.com

2.4. Security Concerns

After setting up everything that’s needed, it’s time to talk a bit about security. And this is not so much different from what we’ve written before when describing how to configure the connection using the Authorization URL. Here we still need to store a file to submit to the login command, but in this case it contains the private key rather than the authorization URL. And, again, it’s not safe to simply store that private key in the file system or repository, depending on whether you’re working on a virtual server, a source control system (linked to a CI/CD tool), etc. Thus, we advise you to store the private key in a safer place, like environment variables. In Section 1.1, we described a bit more how we can do it for CI tools (such as GitHub, Bitbucket, etc.).

Of course, we cannot store a file in an environment variable, but we can store its content there! Afterwards, we only need to save the variable value in a temporary file before executing the login command, as shown below.

echo "${{ secrets.SALESFORCE_JWT_SECRET_KEY }}" > server.key
sf org login jwt --client-id=${{ secrets.SALESFORCE_CONSUMER_KEY }} --jwt-key-file=server.key --username=${{ secrets.SALESFORCE_DEVHUB_USERNAME }} --alias MyOrg

And that’s it. But, again, keep in mind that a private is a private and should not, whatever the circumstance, be shared with anyone. Keep it safe!

While your consumer key and username can be compared to your address, which may be shared with others, the private key may be compared to your home’s keys. So if you accidentally let others see the consumer key and private key, it’s like you lose your home’s keys with your address written in the key chain.

2.5. Video — Authorising Salesforce Org using JWT Flow (+ GitHub Actions implementation)

3. Resources

  1. GitHub Action workflow using Authrization URL
  2. GitHub Action workflow using JWT Flow

4. References:

SFDX Web Login
SF Web Login
SFDX Login AuthURL
SF Login AuthURL
SF Login JWT
Authorize an Org Using the JWT Flow
Create Self-signed certificate
Create Connected App for SFDX
Public/Private keys vs Certificates

--

--