Authenticating SF CLI using a Json Web Token (JWT)

A Comprehensive Comparison.

  • DEVSECOPS
  • 1 OCT 2023
  • blog

Introduction

This post is a little esoteric, we’ve written it as a reference for the benefit of our team, but we are always happy to share knowledge with the community! For some this might be the most exciting page they find this week. That's because using JSON Web Tokens to Auth against SF CLI is 10 odd lines of config that you're never going to stumble onto without a lot of suck it and see.

Lets get some basics done - Why would you use a JWT? This one is really easy, every other OAuth option for connecting to a Salesforce instance requires a screen pop. You can get around this with some really funky code, for example I love logging into Salesforce using curl and a customised SOAP login POST. The great thing about this approach is - it works and means you can get away with username, password and token. The bad news is that you're going to be using clear text passwords at some point so this is not a viable option in a secure environment. Dev is fine but when you are playing with a production pipeline or access this is most definitely NOT the approach you want to take.

Okay so what do we use to login without a screen pop? Welcome to JSON Web Token (JWT). If you're after some background on JWT here's' a great place to start. If you just want to find out how to use it with sf cli (Command Line Interface) - read on.


Enough talking - show us yer code!.

The order I'm doing this is a little backward - that's okay, I'm telling a story here. Using a JWT means you need a Connected App established in your Salesforce instance. There's a number of ways you can do this, I have some very funky code that deploys a new Connected App using the MetaData API from a Sandbox Refresh class but this gets very messy. I might still put that into a later post but for now lets assume you will manually setup the Connected App in your Production org.

The Connected App is pretty easy and you can get away with an absolute mnimimum, there's a great set of instructions here but these still feel a little complex so stepping through the process.

  • Create your Connected App
    Starting at Setup | App Manager, click New Connected App and fill in the Connected App Name, API Name & Contact Email.
    What you use doesn't really matter but I'm sure you want to make them sensible because someone will have to figure out what this Connected App is used for once you exit the building
  • Enable OAuth Settings
    After you click the Enable OAuth settings you will be given a vast array of options, make sure they are all removed! Theres 3 essential items only.
    Callback Url You need this, it doesn't need to actually work but it is needed - enter https://login.salesforce.com/services/oauth2/callback
    Use digital signatures This immediately shows a Browse... button, clicking this allows you to upload your digital certificate, you don't have this yet right? Seer instructions later (I told you this was backwards)
    Selected OAuth Scopes
    See the instructions above for what you need to include - for me I want everything so I choose Full access (full) AND Perform requests at any time (refresh_token, offline_access)
    You need to include both. Once you have sorted that hit Save. That's it you're done!
Okay you're now done to access your Salesforce instance you now execute the following:

    sf org login jwt --client-id 3MVG9.........Vj3AOm --jwt-key-file server-key.pem --username my.username@my.salesforce.instance --instance-url https://mysalesforceinstance.my.salesforce.com --alias SFINSTANCE --set-default
                           
So, where does this stuff come from? The Consumer Key is generated for you when you create the Connected App, to find this go to the option "Manage Consumer Details" the client id is the "Consumer Key". You don't need the Consumer Secret (unless you specify that this needs to be included in your auth exchange).
Now the fun part! Where do you get the server-key.pem from?


Generating your Certificate and JWT.

Welcome to the fun part of the post. From this point on you will be using OpenSSL you can then build the OpenSSL library yourself. This means you wont have any supply chain exposure. The other option is to check your git binaries you will probably have it installed already in Git/usr/bin
To confirm OpneSSL is installed and compiled try running

 
    > openssl version
    > OpenSSL 3.1.2 1 Aug 2023 (Library: OpenSSL 3.1.2 1 Aug 2023)
                           
If OpenSSL is not installed... you'll know!

First thing we need to do is generate a Self Signed Cert, to do this you can follow the steps from here or copy and paste the same from below.

 
    openssl genpkey -des3 -algorithm RSA -pass pass:SomePassword -out server.pass.key -pkeyopt rsa_keygen_bits:2048
    openssl rsa -passin pass:SomePassword -in server.pass.key -out server.key
    openssl req -new -key server.key -out server.csr
    openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
                           
If you're interested in what this actually means.
  • openssl genpkey -des3 -algorithm RSA -pass pass:SomePassword -out server.pass.key -pkeyopt rsa_keygen_bits:2048
    This step generates a private key, output o server.pass.key using the RSA Algorithim and 2048 bit pass key length
  • openssl rsa -passin pass:SomePassword -in server.pass.key -out server.key
    This step removes the pass key from the server.pass.key file and stores the result in server.key
  • openssl req -new -key server.key -out server.csr
    This step uses the server.key without the pass key and then creates a signing request (csr) file
  • openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
    Finally, we create a server.crt based on the signing key request file and the saved key file, we sign this for 365 days using sha256
You now have your Self Signed Certificate file, this is the file that is uploaded against the digital signature option in the Connected App.
Now for the JWT!
 
    cat server.crt server.key > server-key.pem
    //or if you prefer windows
    type server.pem > server-key.pem
    type server-private.pem >> server-key.pem
                           
That's it! Pretty easy.

Okay now for the bad news - once you refresh your instance or create a sandbox from Production, your Connected App Consumer Key will be regenerated so this process wont work. The good news is that all you have to do is login to your new instance and again go to Manage Apps, Manage Consumer Details and copy the Key again.
There are other ways around this and it is possible to find the Consumer Key as its being generated, you can include this into your Sandbox Refresh class or create the Sandbox from the sf cli, next post Ill give you those options and circle back to some of the other code snippits I've mentioned above.

So what does this actually mean? Salesforce generates a unique consumer key/client id for every Connect App. This means if you try and deploy a Connected App with the same client id it will fail. This however is not bad news .... actually its a real blessing because it means you dont have to remember the url of the instance you are registering - you can simply ignore this parameter.


    sf org login jwt --client-id 3MVG9.........Vj3AOm --jwt-key-file server-key.pem --username my.username@my.salesforce.instance --alias SFINSTANCE --set-default
                           
and to open - use sf org open as you would expect

    sf org open --target-org SFINSTANCE 
                           

Thanks again for reading, feel free to reach out if you need some help or support. We're always here to help!