How to configure persistent OAuth tokens in Salesforce | Salesforce Ben (2023)

uDevelopers

Share this article...

Most developers who have worked on integrations are familiar with OAuth 2.0 when authenticating web servers to authenticate with external APIs. At a high level, the Salesforce application calls an external credential-providing API to request an access token. These credentials are validated and the external API issues a token.

Unfortunately, there was no perfect way in the Salesforce platform to maintain the OAuth token in all transactions for its lifetime. Most often, this resulted in setting the application to require this token for each transaction, which consumes unnecessary resources and makes the application less efficient. However, pWinter Edition '23, Salesforce introduced third-party credentials. Along with external credentials came custom headers, permission set mapping, and authorization parameters under permission set mapping. This combination now provides a secure, native, and simple mechanism for storing the authentication token and its valid date/time.

setting

The first step in this process is to create an external credential. To goset upI remove "Named Credentials" uQuick searchbox. ClickNamed credentialsand you should see two tabs, Named Credentials and External Credentials. Go toExternal credentialstab and clickNovi.

How to configure persistent OAuth tokens in Salesforce | Salesforce Ben (1)
(Video) Salesforce: OAuth Access Token Expiration (3 Solutions!!)

Add your tag and your name and chooseCorrectedfor your authentication protocol.

The next step is to create a permission set mapping. You can mirror an existing permission set or create a new one, which should work for the purposes of this guide.

In the Permission Set Mappings section, clickNovi. You don't have to worry about adding authentication parameters because the code will handle generating those parameters.

How to configure persistent OAuth tokens in Salesforce | Salesforce Ben (3)

READ MORE: Learn Salesforce roles and profiles in 5 minutes (with permission sets)

Now, the cool part about this methodology is that you can create custom headers with a formula. Let's imagine this API just passed a token as an authorization header. Instead of specifying it in the code that creates the callout, we can use a custom header formula here as shown below. BelowCustom headerssection, clickNoviand enter the appropriate authorization header.

(Video) Salesforce: OAuth 2.0 JWT Bearer Token Flow - "error":"unsupported_grant_type"

How to configure persistent OAuth tokens in Salesforce | Salesforce Ben (4)

That's it for external credentials! All is ready. Now let's create a named credential.

Switch back to Named Credentials and navigate toNamed credentialsnow card. ClickNoviand set up named credentials. Since we are generating the authorization header using the formula in the custom header, we need to make sure that we exclude the "Generate Authorization Header’ and contain’Allow formulas in the HTTP header' as shown below.

How to configure persistent OAuth tokens in Salesforce | Salesforce Ben (5)

As for the configuration, we are ready!

Grab the code

Now we need to go through the code a bit. Regardless of how you create your vertex classes, create a new class calledConnectAPIand put the following code in it.

encode

public with ConnectAPI sharing class { private final String NAMED_CRED_RELATIVE_URI = '/services/data/v56.0/named-credentials/credential'; public update of HttpResponseNamedCredential(Mapinput, Boolean mappingExistsAlready){ HttpRequest req = new HttpRequest(); req.setEndpoint(Url.getOrgDomainUrl().toExternalForm() + NAMED_CRED_RELATIVE_URI); req.setHeader('Autoryzacja', 'Nositelj' + UserInfo.getSessionId()); req.setHeader('Typ zawartości', 'aplikacja/json'); if(mappingExistsAlready){ req.setMethod('PUT'); } else{ req.setMethod('POST'); } req.setTimeout(120000); req.setBody(JSON.serialize(input)); zwróć nowy Http().send(req); }}
(Video) Salesforce: Authenticated Oauth workflow required constant login

encode

READ MORE: 5 mistakes to avoid when learning Apex Code

Note that you MUST use the Call Endpoint API version 56.0 or later. The reason for this is that the whole thing was released with this API version in the winter of '23, so it's not available before then. Now that we have this class set up, create another class calledTokenHelperand drop the code below there.

encode

public z udostępnianą klasą TokenHelper { private static final String CREDENTIALNAME_TOKEN = 'token'; privatni statički konačni String CREDENTIALNAME_EXPIRES = 'ističe'; public static Boolean checkTokenValidity(String externalCredName){ ConnectApi.ExternalCredential externalCred = ConnectApi.NamedCredentials.getExternalCredential(externalCredName); if(externalCred!= null &&!externalCred.principals.isEmpty()){ ConnectApi.Credential cred = ConnectApi.NamedCredentials.getCredential(externalCredName, externalCred.principals[0].principalName, ConnectApi.CredentialPrincipalType.NamedPrincipal); if(cred != null && cred.credentials.containsKey(CREDENTIALNAME_TOKEN) && cred.credentials.containsKey(CREDENTIALNAME_EXPIRES)){ ConnectApi.CredentialValue wygasa = cred.credentials.get(CREDENTIALNAME_EXPIRES); return (expires.value != null && Datetime.valueOf(expires.value) > Datetime.now()); } } zwróć fałsz; } public static void updateToken(String externalCredName, String newToken, Datetime wygasa){ ConnectApi.ExternalCredential externalCred = ConnectApi.NamedCredentials.getExternalCredential(externalCredName); if(externalCred!= null &&!externalCred.principals.isEmpty()){ ConnectApi.Credential cred = ConnectApi.NamedCredentials.getCredential(externalCredName, externalCred.principals[0].principalName, ConnectApi.CredentialPrincipalType.NamedPrincipal); if(cred != null){ Boolean mappingExistsAlready = istina; Mapainput = convertCredToInput(kredyt); if(((Mapa>)input.get('login details')).keySet().isEmpty()){ mappingExistsAlready = false; } ((Map>)input.get('credentials')).put(CREDENTIALNAME_TOKEN, nowa mapa{ 'encrypted' => true, 'value' => newToken }); ((Mapa>)input.get('credentials')).put(CREDENTIALNAME_EXPIRES, nowa mapa{ 'encrypted' => false, 'value' => String. valueOf(ističe)}); ConnectAPI conAPI = new ConnectAPI(); HttpResponse resp = conAPI.updateNamedCredential(input, mappingExistsAlready); } } } public static mapconvertCredToInput(ConnectApi.Credential cred){ Mapinput = new map(); input.put('authenticationProtocol', String.valueOf(cred.authenticationProtocol)); input.put('externalCredential', String.valueOf(cred.externalCredential)); input.put('nazwa_principal', String.valueOf(cred.principalname)); input.put('principalType', String.valueOf(cred.principalType)); Mapa> credentials = new map>(); for(String credKey : cred.credentials.keySet()){ ConnectApi.CredentialValue credValue = cred.credentials.get(credKey); credentials.put(credKey, nowa mapa{ 'value' => credValue.value, 'encrypted' => credValue.encrypted }); } input.put('credentials', credentials); return entry; }}

encode

Walk through the code

There are several methods inTokenHelperclasses we will go through because they are the heart of this implementation. The first method, calledcheckTokenValidityit just does. It will take the name of the external credential and check if the token is valid. The way this method will define the token as invalid is as follows:

(Video) Salesforce: OAuth 2.0 Username-Password Flow Problem - unsupported_grant_type (2 Solutions!!)

  1. There is no external credential with the given name.
  2. The external credential exists but is not mapped to any permission sets.
  3. The external credential exists and is mapped to an entitlement policy, but the entitlement policy mapping does not contain an expiration date or token authorization parameters.
  4. The external credential exists, is mapped to a permission set, and the permission set mapping contains an expiration date and token authorization parameters, but the expiration date is earlier than the current date and time.

My suggestion is to actually modify this code to throw an error if one of the first two conditions is met to ensure that the code doesn't assume you'll fall into one of the last two situations which are the only situations where you should try to do whatever in regards to getting a new token. We will continue on the happy road.

If the token parameter has not yet been set or the token expiration date has passed, this method will notify you.

Another method is the so-calledToken updatewhich again does exactly what it looks like. This method is called when you have created a token bubble, received a new token and expiration date, and want to set these values ​​on the external credential mapping before creating the bubble. It goes back toConnectAPIclass we created earlier.

Reason why we update named credentials via Connect REST API instead of just usingConnectApi.NamedCredentialsclass this is because using this update class will cause us to encounter a callout exception "You have optional work pending" when we create the actual callout after the token has been updated. I know this because I ran into it when I first implemented it this way. I tested everything piece by piece and everything worked great. Then when I started putting it all together, I got hit. However, if you don't need to use the token in the same transaction, I suggest usingConnectApi.NamedCredentialsclass, which is a little simpler than the hoops we're jumping through here.

How to configure persistent OAuth tokens in Salesforce | Salesforce Ben (6)

We will also use this methodconvertCredToInputhelper method that handles converting an existing named credential entry to a map. The reason why we create a map, not just an instance of itConnectApi.CredentialInputthis is because we can't serialize this object withJSON serializationmethod, so I thought creating the map is a bit less demanding than using the JSON Generator class, but feel free to modify it however you like.

Another thing I want to point out is that we set encrypted to false for the "expires" authorization parameter. As far as I know you can't do this in the UI and you need this value to be unencrypted otherwise your code can't access the value (it will look like null). That said, if you're using this methodology to handle OAuth tokens, don't bother creating these authorization parameters and just let the code set them for you to make sure everything's okay.

Abstract

That's it! By setting this field you can inject it into regular calling code using named credentials and storing your credentials in a safer and more convenient way. In your code just useTokenHelper.checkTokenValidityto see if you need a new token and if so create a bubble for your token and save the token and new expiration date inTokenHelper.updateTokenmethod.

(Video) Salesforce: Reason: invalid_grant - expired access/refresh token

Videos

1. Salesforce: Salesforce to Salesforce authentication using JWT
(Roel Van de Paar)
2. Salesforce: obtain refresh token with no redirect url?
(Roel Van de Paar)
3. Salesforce: How to get Google Calender API access token using refresh token in Apex Salesforce?
(Roel Van de Paar)
4. Salesforce: Invalid_grant:authentication failure when signing on to Salesforce Workbench
(Roel Van de Paar)
5. Salesforce: "unsupported_grant_type" error when trying to authenticate
(Roel Van de Paar)
6. Salesforce: Error on secret-tool for sfdx force:auth:jwt:grant
(Roel Van de Paar)

References

Top Articles
Latest Posts
Article information

Author: Duane Harber

Last Updated: 17/11/2023

Views: 5908

Rating: 4 / 5 (51 voted)

Reviews: 90% of readers found this page helpful

Author information

Name: Duane Harber

Birthday: 1999-10-17

Address: Apt. 404 9899 Magnolia Roads, Port Royceville, ID 78186

Phone: +186911129794335

Job: Human Hospitality Planner

Hobby: Listening to music, Orienteering, Knapping, Dance, Mountain biking, Fishing, Pottery

Introduction: My name is Duane Harber, I am a modern, clever, handsome, fair, agreeable, inexpensive, beautiful person who loves writing and wants to share my knowledge and understanding with you.