OAuth 2.0 JWT Client Credentials Auth Provider using Salesforce Named and External Credentials

A reusable Apex Auth Provider Class for JWT Client Credentials

Justus van den Berg
8 min readMay 1, 2023

A common way to get an OAuth 2.0 access token is using a client_credentials flow that leverages a JWT instead of passing a client id and client secret. It’s known as a “Client Secret JWT” method. Takahiko Kawasaki gives a great explanation in section 1.5 of this article.

In this flow the client application uses a signed JWT that the authorization server can authenticate to verify the client token request is valid.

Salesforce does not support this flow OOTB, so I built a reusable Apex Auth. Provider using the AuthProviderPluginClass class that can be used with Named / External credentials. This plugin generates the request and executes the OAuth 2.0 JWT Client Authentication flow using a client_credentials grant type.

Update: Salesforce supports this flow OOTB since the Winter 24 release. Making this Auth Provider Obsolete for Named Principal flows. It can still be used for a Per User Principal where the JWT Subject represents the user.

This article will explain in detail how to install and configure the Auth. Provider in combination with External and Named Credentials.

TL;DR The Github repository can be found here: https://github.com/jfwberg/lightweight-oauth-jwt-client-credentials-auth-provider.git

Important

Security is no easy subject: Before implementing this (or any) solution, always validate what you’re doing with a certified security expert and your certified implementation partner.

Why a custom authentication provider?

The great advantage of using named and external credentials is that Salesforce takes care of the token process: Retrieving a new token, storing tokens securely and automatically fetching a new token if a token has expired.

Refresh tokens are not supported in this flow; when a token is expired it will fetch a new token. Having Salesforce take care of all the heavy lifting saves you building something yourself resulting in a more secure process.

External Credentials support JWTs for authorization grants OOTB but not yet for client authentication. There is a small difference. What we want to achieve is sending a POST request to the authorization server that looks like this:

POST /token/oauth2 HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded

Body (will be URL encoded)
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
assertion=[JWT compact serialized as a JWS in here]

The Auth Provider (code) explained

I am not going to go to deep into the code. The code is well structured and commented so you should be able to understand. All the details are in the repo.

Prerequisites

  • Some knowledge on this OAuth flow, JWT, JWS, JWKS is recommended as I will not explain these concepts
  • A certificate with the private key that is used for signing the JWT with a JWS that is imported in the Salesforce Certificate Key Store
  • Alternatively you can use a Self Signed Certificate for testing purposes
  • The public key needs to be shared with the authorisation server and setup according to their standards, usually a JWKS
  • You’ll need all the authorization server details that are required to setup the connection an create the JWT

00 :: Deployment and Preparation

  1. Import the JWT Signing Certificate into Salesforce, Note down the Certificate API Name
  2. Deploy the Apex Class and the Custom Metadata (including layouts) from this SFDX Project repository to your Org (Or install the package from the next step)
  3. The package can installed using the following URL: https://[MY_DOMAIN_URL]/packaging/installPackage.apexp?p0=04tP3000000MWfZIAW
  4. Or install as unlocked package: https://[MY_DOMAIN_URL]/packaging/installPackage.apexp?p0=04tP3000000MWndIAG

01 :: Setup the Auth. Provider

In my example I am going to connect an API called “PiMoria”; this my test domain that I will use throughout this example. Replace this with your own API. This is purely to illustrate a naming convention.

  1. In Setup > Auth. Providers, Create a new Auth. Provider Using the OAuthJwtClientCredentials class as the provider type
  2. Populate the Execute Registration As field first, this is a mandatory field that is not marked as mandatory and will reset the entire form if you forget it.
  3. Populate the fields in the the Auth. Provider. The below table details what is required in the fields
  4. Triple check you put in all mandatory fields: If you have forgotten one, you have to re-do them all

When finished it should look like something like this:

02 :: Setup the Remote Site Setting(s)

In order to make API call-outs to the token endpoint securely, we must set up a remote site setting for the token endpoint.

1. Go to Setup > Security > Remote Site Settings, Click New

2. Populate the Remote Site URL field with the Token Endpoint Base URL and set a Name and Description

3. Press Save

4. If your API Base URL is different than your token URL, you need to create separate Remote Site Setting for this API. In my example case, the base URL is the same.

03 :: Create a Permission Set for the External Credential

External Credentials require a Permission Set in order to create a credential type mapping. It’s best practice to create a separate Permissison Set for each Extern Credential to keep a strict separation and stick to the least access principle.

At this stage you don’t have to assign any permissions, this will happen later.

1. In Setup > Users > Permission Sets, Click New

2. Set a Label and an API Name, write the API Name down, we’re going to need this later.

3. Assign the Permission Set to the testing user, probably the user you’re logged in with

04 :: Setup the External Credential

Your Auth Provider is now ready for testing. The next step is to create an External Credential that authenticates to the token endpoint using the Auth Provider.

  1. Go to setup > Security > Named Credentials and click the External Credentials tab
  2. Click New Set A Label and a Name
  3. Set Authentication Protocol to OAuth 2.0
  4. Set Authentication Flow Type to Browser Flow. The Scope field can be left blank. This is overwritten by our Auth. Provider Settings.
  5. Select your created Auth. Provider from the Auth Provider Picklist
  6. Press Save
  1. Scroll down to the Permission Set Mappings section and press New
  2. For the Permission Set, select the Permission Set You created in Step 3.1. Set the Identity Type field to Named Principal. You can ignore the sequence number. This can stay default.
  3. Press Save

05 :: Connect your External Credential through the Auth Provider

You have no finished setting up the external credentials and the auth provider it’s time to test it by calling the token endpoint and authenticate.

1. Click on the arrow button next to the Permission Set Mapping in the actions column

2. Click Authenticate, Salesforce now call the token endpoint and execute the logic from the Apex class to get a token. It will redirect you to the same page (This is the redirect URL that is auto generated in the class.)

3. If you’re successful you get a success message, if you’re unsuccessful you have to start debugging :-) (see the section below for some pointers)

06 :: Create a Named Credential

Once you have successfully authenticated your External Credential, there is one more step required to use it from Apex or Flow to call an API: we need a Named Credential that uses the External Credential.

1. Go to setup > Security > Named Credentials and click the Named Credentials tab

2. Click New (Note we are NOT creating a legacy named credential, we’re modern)

3. Give your credential a Label and a Name

4. Populate the URL field with the base URL of the API (Not the token endpoint). Quite often these are the same.

5. Select your External Credential you created in step 4.2

6. Make sure Generate Authorization Header is selected, this is by default

7. Optionally you can select a namespace and a network if you use private connect.

8. Press Save

07 :: Test the Named Credential

Now you have everything you need. Let’s open up and execute anonymous window and call one of our endpoints.

  • If there is an invalid token, a 401 response code will be received. If this happens the external credential will call the refresh token logic through the Auth Provider Automatically and take care of all of that overhead.
  • “callout:[NamedCredentialApiNAme]:” will be replaced by the base URL as specified in the step 6.4.
  • Update and run the following code snippet and you should be able to successfully call the API:
HttpRequest request = new HttpRequest();
request.setEndPoint('callout:PiMoria/api/authentication-test');
request.setMethod('POST');
HttpResponse response = new HTTP().send(request);
System.debug(response.getBody());

08 :: Debugging common errors

  • Forgotten to add the API / Token URL to the Remote Site Settings
  • Forgotten to assign the permission set
  • Wrong certificate name (Note this is Case Sensitive, you get a “System.NoDataFoundException: Data Not Available” exception if the Certificate API Name is wrong)
  • Any other (JWT) configuration details incorrect. If this is the case the debug logs with show the response. The auth provider throws an OAuthJwtClientCredentials.TokenException if the token endpoint does not return status code 200.

I don’t want to go to deep into debugging but a few pointers:

  • Set the trace flag on the executing user
  • Clean your debug logs in setup, execute the code and see the logs
  • Alternatively, open your developer console to stream the logs

Steps to import a certificate store (JKS) in a Scratch Org when getting “Data Not Available” error message on import

In some cases there is a bug in the certificate import that gives an error if you try to import a JKS It says “Data Not Available”.

I have this issue in all my scratch orgs. There is a simple way to resolve this.

  1. Go to setup > certificate and key management
  2. Create a self signed certificate
  3. Go to Setup >> Identity provider
  4. Click enable Identity Provider and select the self signed certificate you just created and press save
  5. Press the disable button, as we don’t really need it
  6. Go back to Setup > Certificate and Key Management and try to “import from keystore” again, it should work now.

Final note

At the time of writing, I work for Salesforce. The views / solutions presented here are strictly MY OWN and NOT per definition the views or solutions Salesforce would recommend. Again; always consult with your certified implementation partner before implementing anything.

--

--