17

I have a requirement to authenticate to an external API for callouts using the OAuth2 Client Credentials Grant (This was helpful in referencing the different grant types for me). I would like to use Named Credentials as I understand this is what its intended use is rather than having to handle and implement the auth flow, storing and refreshing of tokens yourself.

I haven’t found a lot to go on thus far from the docs or web searches. Looking around (here and here) it seems that this is either not possible or a bit of a hack to implement because of the need to extend the base class AuthProviderPlugin Class that expects, it seems, the Authorization Code Grant flow.

What I can gather is:

  • Named Credentials is the preferred method for authenticating and managing auth to an external API.
  • Named Credentials supports OpenId out of the box but other OAuth flows can be supported by extending the AuthProviderPlugin class.
  • The AuthProviderPlugin Class expects the OAuth implementation to use the Authorization Code Grant Flow.
  • Hence, if you are authenticating to OAuth with Client Credentials flow (or perhaps some other custom auth mechanism that requires getting a token and managing its expiration) you are left writing your own Apex to achieve this.

Are these assumptions correct? And if so, is there some convergence around (documentation, examples, open source library etc.) for managing these non-supported auth requirements?

1
14

In my experience, the bullet points you've highlighted are pretty much on-target. What I would add is that both NamedCredentials and CustomApplications, make the assumption the OAuth provider (the idP) has a publicly accessible Social or other major API along the lines of Facebook, Google, Twitter, etc where you can import a token or some other credential to begin with that identifies the provider. If it doesn't have that capability, it doesn't seem to want to support allowing you to do simple OAuth authentication (as in "handshaking") using protocols with some other endpoint. In essence, if you aren't doing SSO using for Salesforce Login, the Auth Namespace doesn't provide much assistance for use in OAuth custom flows that integration partners may want you to use as part of their API.

Like you, I recently found this very frustrating. See the following question Unusual OAuth JWT Bearer Token Flow (Outbound) Integration Pattern. I wound up having to write my own service to handle an OAuth Username-PW login to obtain a JWT Bearer Token, plus Refresh Token that expired after 12 hours. I couldn't find a way to make named credentials work for me and no adequate documentation to describe how to extend the AuthProviderPlugin in a manner that would allow me to do this I all could fin is Create a Custom Authentication Provider Plug-in which isn't very helpful for this type of use case (again, it assumes SSO for Salesforce login).

In examining the AuthProviderPlugin, and the Auth Namespace in general, what I've concluded is that it is almost entirely written around support for login to Salesforce and negotiation for OAuth where the idP only supports login to Salesforce; not login to other services. That generally seems to be its purpose. It is not written for OAuth negotiations for transactions that take place in the opposite direction.

Two libraries I discovered which may be helpful to you are ffhttp-core and ffhttp-core-samples. Both still expect an external provider which can include Salesforce as the idP, but are written for connecting to external services like Box (the example application illustrated in ffhttp-core-samples), GoogleDrive, etc and can be customized for any OAuth flow that you need to use. I chose not to implement ffhttp-core for my purposes, but found it helpful in figuring out what I needed to do in order to write my code.

2
  • Thanks for this clarification. That really helps. I have looked into the ffhttp libraries and the other examples from your question and have a class that is close to solving this now. Did you end up implementing the session management for the token? I was keen on using Custom Settings for this but the token is 800 odd chars and there is no Long Text Area field in Custom Settings. I'm currently using a custom object as the Custom Metadata API Apex deploy method seemed overly complicated for the job. Jul 17 '18 at 7:20
  • I'm using custom settings for this application. I will ultimately hold some of the static settings in metadata (is still a work in progress that's nearly complete). I didn't want to have to "jump through hoops" to get code coverage in unit tests where I'd need to do DML on custom metadata as token values changed; something easily handled with custom settings. For benefit of others who might search this subject at a later time, if this post answers your question, please mark it as accepted.
    – crmprogdev
    Jul 17 '18 at 11:33
9

Bit of a hack indeed, but there is a way to use Named Credentials to implement the Client Credentials flow so you don't have to manage access or refresh tokens yourself. All credit for figuring this out should go to Chuck Liddell, who did a brilliant Pluralsight course on this topic that I highly recommend (most relevant part to Client Credentials flow starts here: https://app.pluralsight.com/course-player?clipId=fbc87f2d-7675-418d-bba9-920248983ff7)

Step 1. Create a Custom Metadata Type (Name: Client Credentials Auth Provider Definition, or similar):

  • Create 3 custom text fields on it:
    • Client Id
    • Client Secret (WARNING: this will be visible in plain text in the Setup menu)
    • Token Endpoint
  • You don't need to create any Custom Metadata records for this type
  • This will define what fields are available to fill in later on your custom Auth Provider

Step 2. Create an Apex class (Name: {YourThirdPartyService}ClientCredential, or similar) that extends 'Auth.AuthProviderPluginClass' (or copy Chuck's class from here and tweak it: https://github.com/grekker/pbp-authenticating-external-apps/blob/master/force-app/main/default/classes/CustomAuthProvider.cls)

  • Make sure your getCustomMetadataType method references the API name of the Custom Metadata Type you created in Step 1
  • The name of the Apex class will now appear in the list of options for 'Provider Type' when creating a new Auth Provider

Step 3. Set up a custom Auth. Provider with:

  • Provider Type: <Apex class name from Step 2>
  • Client Id from your third party service
  • Client Secret from your third party service
  • Token Endpoint from your third party service
  • Update the Apex class' redirectUrl variable to end with the URL Suffix defined in your new Auth Provider

Step 4. Set up a Named Credential with:

  • URL: <your third party service's resource endpoint baseURL>
  • Identity Type: Named Principal
  • Authentication Protocol: OAuth 2.0
  • Authentication Provider: <Auth Provider from Step 3>
  • Start Authentication Flow on Save = checked
  • Generate Authorization Header = checked

Upon saving, the Named Credential should get the access token back and store it securely. From this point forward, callouts using this Named Credential should work the same as any other, more standard Named Credential

7
  • You shouldn't know how to make the AuthProviderPluginClass POST for at token instead of GETting ? Microsoft Graph, wants a POST req for a new token instead of a GET ...
    – Carl-Johan
    Sep 29 '20 at 13:38
  • 3
    Hi @Carl-JohanBeurling, I think I found an example using a POST on github- see line 54 here: github.com/jamesmelville/AuthProviderPluginClass/blob/master/… Sep 30 '20 at 21:25
  • 1
    Holy moly - your link pushed me in the right direction, and with some extra modifying, it now actually works. I've spent the past 3 days on this! Thank you!!!
    – Carl-Johan
    Oct 1 '20 at 7:40
  • 1
    Glad to hear it! Oct 2 '20 at 17:14
  • 2
    Check out this blog post by my good friend Mikkel. He has created a repo where you can see the code. He explains the setup in his post: lekkimworld.com/2021/05/07/…
    – Carl-Johan
    Jun 11 '21 at 13:32

Not the answer you're looking for? Browse other questions tagged or ask your own question.