2

The following Apex code uses Metadata and SOAP API to create a NamedCredential and Custom Auth Provider to connect to a public REST API that is authenticated via OAuth.

public override PageReference run() {
    createAuthProvider();
    createNamedCredential();

    return oAuthKickoffUrl();
}

private void createAuthProvider() {
    createSObject('<urn1:type>AuthProvider</urn1:type>' +
        '<urn1:DeveloperName>' + DEV_NAME + '</urn1:DeveloperName>' +
        '<urn1:FriendlyName>' + DEV_NAME + '</urn1:FriendlyName>' +
        '<urn1:ProviderType>OpenIdConnect</urn1:ProviderType>' +
        '<urn1:OptionsSendAccessTokenInHeader>true</urn1:OptionsSendAccessTokenInHeader>' +
        '<urn1:OptionsSendClientCredentialsInHeader>false</urn1:OptionsSendClientCredentialsInHeader>' +
        '<urn1:ConsumerKey>' + CLIENT_KEY + '</urn1:ConsumerKey>' +
        '<urn1:ConsumerSecret>' + CLIENT_SECRET + '</urn1:ConsumerSecret>' +
        '<urn1:AuthorizeUrl>' + API_ENDPOINT + '/oauth/authorize</urn1:AuthorizeUrl>' +
        '<urn1:TokenUrl>' + API_ENDPOINT + '/api/v1/oauth/token/</urn1:TokenUrl>'
    );
}

private void createNamedCredential() {
    MetadataService.NamedCredential cred = new MetadataService.NamedCredential();
    cred.fullName = DEV_NAME;
    cred.label = DEV_NAME;
    cred.allowMergeFieldsInBody = false;
    cred.allowMergeFieldsInHeader = false;
    cred.authProvider = DEV_NAME;
    cred.generateAuthorizationHeader = true;
    cred.principalType = 'NamedUser';
    cred.protocol = 'Oauth';
    cred.authProvider = DEV_NAME;
    cred.endpoint = API_ENDPOINT + '/v3';

    create(cred);
}

private PageReference oAuthKickoffUrl() {
    PageReference result = new PageReference(authProvider.OauthKickoffUrl);

    String startUrl = '/' + String.valueOf(namedCredential.Id).left(15);
    result.getParameters().put('startURL', startUrl);
    
    return result;
}

Everything is created properly and looks ok but the Kickoff Call fails with a page of the API provider saying

Error: invalid_request: Invalid "redirect_uri" in request.

    https://provider.com/oauth/authorize
    ?response_type=code
    &client_id=bJE3456345njoH77n5hdGYMV3a
    &redirect_uri=https%3A%2F%2Ftrusted-saltedcaramel-234-dev-ed.cs163.my.salesforce.com
    %2Fservices%2Fauthcallback%2FProvider_Integration&state=CAAAAX94cbTCMDAwM

(I have used this approach successfully with many other APIs without such issues.)

13
  • 1
    Could you add what System.debug says about your redirectUrl variable (with all the confidential stuff removed)? Mar 10, 2022 at 21:13
  • 1
    The header that you've provided is even better. But would you not (based on your code) expect "services/auth/xds" to appear in the redirect_uri? (Sorry, if this question is not advancing the solution.) Mar 11, 2022 at 10:07
  • 1
    @FelixvanHove Don't worry, you help a lot. Are you saying the API Provider needs to know the redirect URI in advance? How would that work? Many different Salesforce orgs with changing mydomains will use this oauth flow. Do I need a Connect App? This made me think that help.salesforce.com/s/… Mar 11, 2022 at 11:57
  • 1
    If the uri is not registered, anyone between you and the authorization server could replace it with another uri - and off you go! Or not?! Mar 11, 2022 at 12:04
  • 1
    @RobertSösemann, I would need to look into this myself, but I belive the Salesforce documentation you thought of (OpenID Connect Dynamic Client Registration) is pertinent. Mar 11, 2022 at 13:54

1 Answer 1

0

Note: Even if it is a bit embarrassing that this question just showed my own misunderstanding of OAuth, let me add as an answer how we resolved the issue.

Everything on the Salesforce side was correct but our assumption that the External Service Provider could just accept arbitrary Callback URL was plain wrong.

Even if some tools allow wildcards on Redirect URIs this is super insecure and should not be done.

We now register every new client / Salesforce subscriber at the External Provider first so they can know the exact Redirect UI in advance.

You must log in to answer this question.

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