[SalesForce] Oauth 2.0 Client Credentials – Custom Auth Provider

I am building out a Salesforce Connect customer adapter to translate rest responses into external data objects.

To authenticate these requests, I would like to use Oauth 2.0's client credentials flow as a custom auth provider.

While developing the AuthProviderPlugin, I am running into two roadblocks, the required initiate and getUserInfo methods.

For the initiate method I am required to return a page reference, but since this is a client credentials flow, I do not need a user to login, I only need to provide the client id, client secret, and access token url.

For the client crendentials flow, what page reference should I return
if I do not need a user to login?

I know I need to get the token, but it looks like that should be handled in the handleCallBack method.

For the getUserInfo method, I do not have or need user info, I am simply looking to create an auth provider using Oauth 2.0's client credentials flow to use in my Salesforce Connect Adapter.

Can I return an empty Auth.UserData object back to satisfy the
required return?

Lastly…

To me it appears the AuthProviderPluginClass and it's custom adapters
do not support Oauth 2.0's client credentials flow by design… is
this true? If so, what would be the recommended approach to unattended
authentication? Open Id Connect?

Code In Progress:

global class myAuthProvider extends Auth.AuthProviderPluginClass {

  // Use this URL for the endpoint that the 
  // authentication provider calls back to for configuration.
  // *****Where do I need to redirect to?*****
  public String redirectUrl = 'https://REDIRECTWHERE?.COM'; 
  private String clientId;
  private String secret;

  // Application redirection to the website for 
  // authentication and authorization.
  //*****How is this different from the accessTokenUrl?*****
  private String authUrl;  

  // URI to get the new access token
  private String accessTokenUrl; 

  // Api name for the custom metadata type created for this auth provider.
  private String customMetadataTypeApiName = 'myMetaDataApiName'; 

  // Api URL to access the user
  // *****I do need to access the user.*****
  private String userAPIUrl; 

  // Version of the user api URL to access data
  // *****I do not need user data.***** 
  private String userAPIVersionUrl; 

  global String getCustomMetadataType() {
      return customMetadataTypeApiName;
  } 

  global PageReference initiate(Map<string,string> 
    authProviderConfiguration, String stateToPropagate) 
    { 

        clientId = authProviderConfiguration.get('Client_Id__c'); 
        secret = authProviderConfiguration.get('Client_Secret__c'); 
        accessTokenUrl = authProviderConfiguration.get('Access_Token_Url_c');

        String url = accessTokenUrl + '?grant_type=client_credentials&client_id=' + clientId + '&client_secret=' + secret + '&redirect_uri=' + redirectUrl;
        //*****Where do I need to redirect to since this is a client credentials flow, not a user login flow?*****
        return new PageReference(url); 

    } 

    global Auth.AuthProviderTokenResponse handleCallback(Map<string,string> 
    authProviderConfiguration, Auth.AuthProviderCallbackState state ) 
    { 
        // Here, the developer will get the callback with actual protocol. 
        // Their responsibility is to return a new object called 
        // AuthProviderTokenResponse. 
        // This will contain an optional accessToken and refreshToken 
        clientId = authProviderConfiguration.get('Client_Id__c'); 
        secret = authProviderConfiguration.get('Client_Secret__c'); 
        accessTokenUrl = authProviderConfiguration.get('Access_Token__c'); 

        Http http = new Http();
        httpRequest request = new httpRequest();
        request.setHeader('Content-Type', 'application/x-www-form-urlencoded');
        request.setHeader('Content-Length', '0');

        String url = accessTokenUrl;
        request.setEndpoint(url); 

        request.setBody('grant_type=client_credentials&client_id=' + clientId + '&client_secret=' + secret);
        request.setMethod('POST');

        HttpResponse res = new httpResponse();
        system.debug(request);
        res = http.send(request);
        string getTokenString = res.getBody();
        Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(getTokenString);
        string getToken;
        getToken = String.valueOf(results.get('access_token'));

        //*****What if I do not receive/need a refresh token, non-expiring token*****
        return new Auth.AuthProviderTokenResponse('myAuthProvider', getToken, 
        'refreshToken', getToken); 
    } 

    //*****I do not need user data, but a UserData Object must be returned. Can I compile with blank data?*****
    global Auth.UserData getUserInfo(Map<string,string> 
    authProviderConfiguration, 
    Auth.AuthProviderTokenResponse response) 
    { 
        //Here the developer is responsible for constructing an 
        //Auth.UserData object 
        String token = response.oauthToken; 
        HttpRequest req = new HttpRequest(); 
        userAPIUrl = authProviderConfiguration.get('API_User_Url__c');
        userAPIVersionUrl = authProviderConfiguration.get
        ('API_User_Version_Url__c'); 
        req.setHeader('Authorization', 'OAuth ' + token); 
        req.setEndpoint(userAPIUrl); 
        req.setHeader('Content-Type','application/x-www-form-urlencoded'); 
        req.setMethod('GET'); 

        Http http = new Http(); 
        HTTPResponse res = http.send(req); 
        String responseBody = res.getBody(); 
        String id = '';
        String fname = ''; 
        String lname = ''; 
        String flname = ''; 
        String uname = ''; 
        String locale = ''; 
        Map<String,String> provMap = new Map<String,String>(); 
        return new Auth.UserData(id, fname, lname, flname, uname, 'what', locale, null, 'myAuthProvider', null, provMap); 
    } 


}

This related questions appears to support the statement that AuthProviderPluginClass does not support the client credentials flow, but I may be misinterpreting: Custom Auth Provider usage for unattended OAuth flow

Best Answer

I've written a custom authprovider which I've shared "as-is" on Github. It implements both the Client Credentials and JWT Bearer variant (without a certificate). Feel free to fork from it. https://github.com/bobbywhitesfdc/ApigeeAuthProvider