1

I am trying to authenticate an external service while using a custom authenticator with client credentials, but when I click save I get the error "Remote_Error: The remote service returned an error".

I'm trying to add debug logs on my code to check if there are any errors, but I am only getting the debug logs from the Initiate() function.

    public static final String CUSTOM_METADATA_TYPE = 'GPA_Api_Auth_Setting__mdt';
public static final String AUTH_PROVIDER_NAME = 'GPApiAuth';
public static final String RESOURCE_CALLBACK = '/services/authcallback/';
public static final String DEFAULT_TOKEN_TYPE = 'BearerToken';
public static final String ENCODING_XML = 'application/x-www-form-urlencoded;charset=UTF-8';
public static final String ENCODING_JSON = 'application/json';

private static String redirectUrl = 'https://myauth.sandbox.my.salesforce.com/services/authcallback/GPAs_custom';

// declarations
private IRefreshTokenGenerator generator = new DefaultRefreshTokenGenerator();

public myClassContructor() {
    super();
    System.debug('Constructor called');
}

public myClassContructor(IRefreshTokenGenerator gen) {
    this.generator = gen;
}

public String getCustomMetadataType() {
    return CUSTOM_METADATA_TYPE;
}

public PageReference initiate(Map<String, String> config, String stateToPropagate) {
    System.debug(LoggingLevel.WARN, 'initiate-config: ' + config);
    System.debug(LoggingLevel.WARN, 'initiate-stateToPropagate: ' + stateToPropagate);
    String url = config.get('Callback_URL__c');
    final PageReference pageRef = new PageReference(url); //NOSONAR
    pageRef.getParameters().put('state',stateToPropagate);
    System.debug(LoggingLevel.WARN, pageRef.getUrl());
    return pageRef;
}

public Auth.AuthProviderTokenResponse handleCallback(
    Map<String, String> config,
    Auth.AuthProviderCallbackState callbackState
) {
    System.debug(LoggingLevel.WARN, 'in handle callback');
    // get access token
    final String access_token = this.retrieveToken(config);
    System.debug(LoggingLevel.WARN, 'accessToken');
    final String refresh_token = this.generateRefreshToken();
    final String state = callbackState.queryParameters.get('state');
    System.debug(LoggingLevel.WARN, 'state');
    return new Auth.AuthProviderTokenResponse('GPAs_custom', access_token, refresh_token, state);
}



   public Auth.UserData getUserInfo(Map<string,string> authProviderConfiguration, 
    Auth.AuthProviderTokenResponse response) {
    System.debug(LoggingLevel.WARN, 'in USER INFO');

        return new Auth.UserData('fakeId', 'first', 'last', 'full', 'email', 'link', 
        null, null, null, null, null);
}

public override Auth.OAuthRefreshResult refresh(Map<String, String> config, String refreshToken) {
    // generate dummy refresh token and get new access token
    System.debug(LoggingLevel.WARN, refreshToken);

    final String newRefreshToken = this.generateRefreshToken();
    final String access_token = this.retrieveToken(config);
    return new Auth.OAuthRefreshResult(access_token, newRefreshToken);
}

private String retrieveToken(Map<String, String> config
) {
    System.debug(LoggingLevel.WARN, 'in RETRIEVE TOKEN');
    String username = config.get('Client_Id__c');
    String password = config.get('Client_Secret__c');
    String endpoint = config.get('Token_URL__c');
    string body = 'grant_type=client_credentials';
    
    System.debug(LoggingLevel.WARN, 'in retrieve');

    Blob headerValue = Blob.valueOf(username + ':' + password);
    String authorizationHeader = 'Basic ' +  EncodingUtil.base64Encode(headerValue);
    
    HttpRequest request = new HttpRequest();
    request.setMethod('POST');
    request.setEndpoint(endpoint);
    request.setHeader('Content-Type', 'application/x-www-form-urlencoded');
    request.setHeader('Authorization', authorizationHeader);
    request.setBody(body);
    System.debug(LoggingLevel.WARN, request);

    HTTPResponse res = new Http().send(request); 
    System.debug(LoggingLevel.WARN, res);

    
    return this.getTokenFromHttpResponse(res);
}

/**
 * Get the access_token from Http response.
 *
 */
private String getTokenFromHttpResponse(HttpResponse res) {
    System.debug(LoggingLevel.WARN, 'in GET TOKEN');
    final Map<String, Object> data = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
    final String errorCode = (String) data.get('error');
    if (String.isNotEmpty(errorCode)) {
        String errorDesc = (String) data.get('error_description');
        throw new Azure_ClientCredentials_Exception(errorDesc)
            .setDetails(errorCode, (String) data.get('trace_id'), (String) data.get('correlation_id'));
    } else {
        return (String) data.get('access_token');
    }
}

private String getAndEncode(Map<String, String> config, String key) {
    System.debug(LoggingLevel.WARN, 'in get encode');
    return EncodingUtil.urlEncode(config.get(key), 'UTF-8');
}

private String generateRefreshToken() {
    System.debug(LoggingLevel.WARN, 'in generate refresh');
    return this.generator.generate();
}

I've tried to look for information about this online, but I haven't been able to find anything, I am guessing there is an issue with the callback state.

Additional info: The external service requires a POST request to get the token, that's why I'm currently unable to use out of the box functionality, external credentials are using GET request by default.

5
  • Does this answer your question? External + Named Credential with oAuth client credentials flow
    – identigral
    Commented Aug 30, 2023 at 21:28
  • @identigral I tried this already, it does not work because Salesforce is using a GET request by default to the token endpoint, but the service that I am using requires a POST. request.
    – peepoCode
    Commented Aug 30, 2023 at 21:39
  • Do you have evidence of Named/Ext credential not working? If so, please edit your question and provide the logs. oAuth RFC for client_credentials requires the token acquisition to be encoded via application/x-www-form-urlencoded, POST is the default for sending that. If Named/External Credential were not doing this correctly, it would mean that Salesforce has completely messed up a significant feature of their major release. It would also mean that the example linked above should not work yet it does.
    – identigral
    Commented Aug 30, 2023 at 22:02
  • Strange, 5 months ago I was able to get my Custom Auth Provider working. Able to debug the handleCallback method and beyond. Fast forward to today, I also not able to get any debug logs after the initiate method. What did Salesforce suddenly change??
    – compski
    Commented Oct 3, 2023 at 7:49
  • I figured out why. You need to do add Setup > Remote Site Settings or preferably Named Credentials method That is why the debug logs are not appearing.
    – compski
    Commented Oct 12, 2023 at 8:28

0

You must log in to answer this question.