7

I have read the full documentation and have few questions on how named credential works.

I have written a custom provider like this:

/**
 * Description of the class
 * <p /><p />
 * @author ****
 */
global class MulesoftAuthProvider extends Auth.AuthProviderPluginClass {

    public String redirectUrl; // use this URL for the endpoint that the authentication provider calls back to for configuration
    private String key;
    private String secret;
    private String authUrl;    // application redirection to the Concur website for authentication and authorization
    private String accessTokenUrl; // uri to get the new access token from concur  using the GET verb
    private String customMetadataTypeApiName  = 'MulesoftInternalAuthProvider__mdt'; // api name for the custom metadata type created for this auth provider
    private String userAPIUrl; // api url to access the user in concur
    private String userAPIVersionUrl; // version of the user api url to access data from concur


    global String getCustomMetadataType() {
        return customMetadataTypeApiName;
    }

    global PageReference initiate(Map<string,string> authProviderConfiguration, String stateToPropagate) {
        /*//authUrl = authProviderConfiguration.get('Authorize_Endpoint_URL__c');
        accessTokenUrl = authProviderConfiguration.get('Token_Endpoint_URL__c');
        key = authProviderConfiguration.get('ConsumerKey__c');
        secret = authProviderConfiguration.get('ConsumerSecret__c');
        //Here the developer can build up a request of some sort , Ultimately they’ll return a URL where we will redirect the user
        //String url = accessTokenUrl + '?grant_type=client_credentials&client_id=' + key + '&client_secret=' + secret;
        return new PageReference(url);*/

        authUrl = authProviderConfiguration.get('Authorize_Endpoint_URL__c');
        key = authProviderConfiguration.get('ConsumerKey__c');
        //Here the developer can build up a request of some sort , Ultimately they’ll return a URL where we will redirect the user
        String url = authUrl + '?client_id=' + key + '&client_=' + redirectUrl + '&state=' + stateToPropagate;
        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 AuthProviderToken
        //This will contain an optional accessToken and refreshToken
        key = authProviderConfiguration.get('ConsumerKey__c');
        secret = authProviderConfiguration.get('ConsumerSecret__c');
        accessTokenUrl = authProviderConfiguration.get('Token_Endpoint_URL__c');
        System.debug('state--->' +state);

        Map<String,String> queryParams = state.queryParameters;
        String code = queryParams.get('code');
        String sfdcState = queryParams.get('state');

        HttpRequest req = new HttpRequest();
        String url = accessTokenUrl + '?code=' + code + '&client_id=' + key + '&client_secret=' + secret;
        req.setEndpoint(url);
        req.setHeader('Content-Type', 'application/xml');
        req.setMethod('GET');

        Http http = new Http();
        HTTPResponse res = http.send(req);
        String responseBody = res.getBody();
        String token = getTokenValueFromResponse(responseBody, 'Token', null);

        return new Auth.AuthProviderTokenResponse('Concur', token, 'refreshToken', sfdcState);
    }


    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/xml');
        req.setMethod('GET');

        Http http = new Http();
        HTTPResponse res = http.send(req);
        String responseBody = res.getBody();
        String id = getTokenValueFromResponse(responseBody, 'LoginId', userAPIVersionUrl);
        String fname = getTokenValueFromResponse(responseBody, 'FirstName', userAPIVersionUrl);
        String lname = getTokenValueFromResponse(responseBody, 'LastName', userAPIVersionUrl);
        String flname = fname + ' ' + lname;
        String uname = getTokenValueFromResponse(responseBody, 'EmailAddress', userAPIVersionUrl);
        String locale = getTokenValueFromResponse(responseBody, 'LocaleName', userAPIVersionUrl);
        Map<String,String> provMap = new Map<String,String>();
        provMap.put('what1', 'noidea1');
        provMap.put('what2', 'noidea2');
        return new Auth.UserData(id, fname, lname, flname, uname,
            'what', locale, null, 'Concur', null, provMap);
    }

    private String getTokenValueFromResponse(String response, String token, String ns) {
        Dom.Document docx = new Dom.Document();
        docx.load(response);
        String ret = null;

        dom.XmlNode xroot = docx.getrootelement() ;
        if (xroot != null) {
            ret = xroot.getChildElement(token, ns).getText();
        }
        return ret;
    }
}

Now see the initiate snippet:

global PageReference initiate(Map<string,string> authProviderConfiguration, String stateToPropagate) {
    /*//authUrl = authProviderConfiguration.get('Authorize_Endpoint_URL__c');
    accessTokenUrl = authProviderConfiguration.get('Token_Endpoint_URL__c');
    key = authProviderConfiguration.get('ConsumerKey__c');
    secret = authProviderConfiguration.get('ConsumerSecret__c');
    //Here the developer can build up a request of some sort , Ultimately they’ll return a URL where we will redirect the user
    //String url = accessTokenUrl + '?grant_type=client_credentials&client_id=' + key + '&client_secret=' + secret;
    return new PageReference(url);*/

    authUrl = authProviderConfiguration.get('Authorize_Endpoint_URL__c');
    key = authProviderConfiguration.get('ConsumerKey__c');
    //Here the developer can build up a request of some sort , Ultimately they’ll return a URL where we will redirect the user
    String url = authUrl + '?client_id=' + key + '&client_=' + redirectUrl + '&state=' + stateToPropagate;
    return new PageReference(url);
}

Now named credentials makes it mandatory to use a redirection logic, which normal oauth has, user has to navigate to the provider site, give his credentials and then get a code, with that code he will exchange it with access token in handleCallback method.

But what if I want to use client credentials oauth flow with named credentials, this wont work. Client credentials directly passes Client key and Client secret in the header to get access token.

I tried changing the initiate logic to this:

//authUrl = authProviderConfiguration.get('Authorize_Endpoint_URL__c');
    accessTokenUrl = authProviderConfiguration.get('Token_Endpoint_URL__c');
    key = authProviderConfiguration.get('ConsumerKey__c');
    secret = authProviderConfiguration.get('ConsumerSecret__c');
    //Here the developer can build up a request of some sort , Ultimately they’ll return a URL where we will redirect the user
    String url = accessTokenUrl + '?grant_type=client_credentials&client_id=' + key + '&client_secret=' + secret;
    return new PageReference(url);

but it then redirects to access token page and named credential authorization is still showing pending.

If i return null, then it gives internal server error.

Any workaround for this?

6
  • Just wanted to say thanks for posting this question as it is the first implementation of this I have seen and the documentation is not that helpful. I do not have anything to add that would help but believe this will be very helpful to the community so +1 from me
    – Eric
    Sep 6, 2018 at 2:33
  • Thanks @Eric for your comments, I am stuck here, lets see if sfdcfox or anybody from community has to share any views on this. Sep 6, 2018 at 2:49
  • I see that the twitter API does not use a "Authorization URL" , can someone suggest how have they managed to authenticate through named credential, like code wise, how have they overcome the redirection part. Sep 6, 2018 at 6:24
  • I have the exact same problem; Happy to have found this early in my development cycle. Sep 17, 2018 at 7:26
  • Did you find a solution to this?
    – Robs
    Oct 4, 2018 at 19:28

1 Answer 1

1

I have detailed out the steps here, Client credentials flow with Named credentials
And code repo is here: https://github.com/cpavnn/sf-client-credentials-flow

The trick lies in the second method initiate which returns the URL where the user is redirected for authentication.
Here we directly call the callback URL of salesforce instead of opening the authentication page, skipping the other steps
Named credentials thinks, we have covered Steps 1 to 5 client credentials flow

The idea of this was borrowed from Chuck lidell, which he discussed in a pluralsight course: https://www.pluralsight.com/courses/play-by-play-authenticating-external-app-service-integrations-salesforce

2
  • The links added to the answer are not working, Is there any other source where I can access the material, even the repo doesn't exist now
    – ScoRpion
    Jan 17, 2023 at 15:55
  • The Github repo and Pluralsight links should be accessible substack has taken my article down, let me try to move it to other blogging platform
    – Pavan
    Jan 19, 2023 at 6:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .