29

For years I have used Salesforce APIs from within Apex. Mostly to enhance my apps with Setup automation. So I used the Tooling and Metadata API from code snippets that used UserInfo.getSessionId() and looked like that:

private HTTPResponse createSObject(String fragment) {
    HttpRequest request = new HttpRequest();

    request.setEndpoint(URL.getOrgDomainUrl().toExternalForm() + '/services/Soap/u/53.0');
    request.setMethod('POST');
    request.setHeader('Content-Type', 'text/xml; charset=UTF-8');
    request.setHeader('SOAPAction', 'ignored');
    request.setBody('<?xml version="1.0" encoding="UTF-8"?>.... 
                     <sessionId>' + UserInfo.getSessionId() + '</sessionId>');

    return new Http().send(request);
}

Also, many other well-known, widely used OSS libraries use this approach. The best example is the Apex Metadata API from Andy Fawcett.

Now a former customer and apparently many other long-time ISV partners, failed a Security Review with similar code:

Unauthorized use of SessionID

Please use OAuth, (try JWT flows and look into admin pre-authorization). Do not touch user credentials in a managed package.

I have no clue what they want. Add a Connected App, Named Credential, and a ton of other metadata for a simple API loopback.

Please advise how to do it right.

11
  • 6
    This surfaced over on the Partner Forums a few months ago. JWT flow OAuth is available with Connected Apps. We handle our Connected App by creating it on our PBO - I recommend against packaging the Connected App since that causes every install to have unique values and requires manual post-install activities to make the necessary parameters available for your JWT flow invocation. That said, you already know that ;).
    – Phil W
    Nov 3 at 17:14
  • 7
    (And, yes, I think this is a crazy policy. Salesforce Security going too far.) Half the time you wouldn't need to use such nasty loopbacks if the platform provided Apex-callable APIs in the first place. The Salesforce API landscape, as broad and significant as it is, is too siloed and too fragmented. You really should be able to invoke all the APIs through Apex proxies IMHO without any actual loopback needed.
    – Phil W
    Nov 3 at 17:20
  • 1
    Agree on the Apex proxies but disagree on the policy with respect to 'external' APIs. Using session ID is completely out of date with respect to modern security best practices. Yes, SF has lots of gaps with respect to "easy to use" metric but compromising on security isn't a solution.
    – identigral
    Nov 3 at 18:34
  • 5
    My problems here: 1. documentation still recommends this approach. 2. Salesforce Security can see, during the review, how the session ID is being used and could easily allow for benign usages such as this. The point is that the session ID isn't being exposed outside the org; it's just being used to loopback to APIs that SHOULD be available without resorting to REST calls.
    – Phil W
    Nov 3 at 19:34
  • 3
    @identigral would you be able to describe what an ISV partner should do in detail to replace such a flaky solution with a proper one? I mean what should be packaged, and what needs to be configured after installation? Which parts can be automated, etcs. Please share your insights. Nov 3 at 20:20

3 Answers 3

3

This is part of the JWT Token Client code from my PoC.

This code uses a Named Certificate. This is great for testing in unmanaged code. I'd need to find another way of providing the certificate from Protected Custom Metadata. The certificate would be company proprietary and must be kept secure.

This idea (Package Certificate Idea) suggests that what using Managed Certificates is not possible. No certificate, no use of the Auth.JWS class. I can manually build a JWT and manually create the signature using the Crypto classes, but it's messier to do.

This code could be usable in a managed package if each Org created their own Connected App and provided the Certificate and Client ID through settings. That sounds horrific though.

    // This may be test.salesforce.com
    String tokenEndpoint = 'https://login.salesforce.com/services/oauth2/token';

    Auth.JWT claims = new Auth.JWT();
    claims.setAud(tokenAudience);
    claims.setIss(settings.OAuthClientId__c);
    claims.setSub(UserInfo.getUserName());
    claims.setAdditionalClaims(new Map<String, Object>{ 'scope' => 'api' });

    Auth.JWS token = new Auth.JWS(claims, settings.OAuthCertificateName__c);
    String tokenString = token.getCompactSerialization();

    Auth.JWTBearerTokenExchange bearer = new Auth.JWTBearerTokenExchange(tokenEndpoint, token);

    return bearer.getAccessToken();

Further to Robert's answer I have tried to create a Web Flow.

Connected App Configuration for Web Flow

I have manually invoked this

https://builder-sagittarius-3050-dev-ed.scratch.my.salesforce.com/services/oauth2/authorize?response_type=code&client_id=REDACTED&redirect_uri=https%3A%2F%2Ftest.salesforce.com%2Fapex%2FRCTest

The OAuth grant was fine, but I am (as requested) redirected to the login page and asked to log in again. I enter my username and password (which is ugly as part of an oAuth flow) and am directed to the VF Page with a code parameter.

Second time in my oAuth grant was automatic and I appeared straight at the login page. So even if the app is granted then the user has to log in every time a token is requested.

1
  • I've been experimenting with a Legacy Named Credential. No luck so far. I can get the call to happen with no error, but I get an empty body and HTTP 302 Found. I also have to place my custom domain in the named credential. I'd need a generic domain if this is to be packaged. Nov 11 at 15:11
8

The OAuth flow we used for our AppExchange app is Web Server Flow for Web App Integration. There are various types of flows that can be used as per your requirements; we used Web Server Flow for Web App Integration.

  1. Once the subscriber installs the application in Salesforce, it lands on a custom app's screen where they are asked to authorize themselves; they click on a button that redirects the user to the login page, and the OAuth flow kicks in.
  2. After providing login details, the user sees the app permission screen. enter image description here
  3. Users can review these permissions and allow or deny them.
  4. Once they allow, Salesforce OAuth flow grants an authorization code in the callback URL
  5. Based on this authorization code, we can request an access token
  6. Now, the app's logic can use this access token to make various API calls to Salesforce services like tooling API, metadata API, etc
  7. This access token has a scope per the permissions selected on connected app settings.
6
  • 1
    Thank you for sharing Chirag. A couple of related questions: (1) Does every user of the app need to go over the oAuth flow or just an admin ? (2) Where do you keep the refresh_token and access_token, in Salesforce ?
    – Fernando
    Nov 7 at 19:27
  • @Fernando 1 - Yes. 2 - Store them securely within Salesforce. Nov 8 at 6:25
  • I'd prefer the JWT Bearer Flow described in another answer because it does not require implementation of an interactive OAuth authorisation flow. Nov 8 at 9:57
  • @Fernando - If you're storing data that is unique to the org, for example a refresh token in this case, then you should use a Protected Custom Setting. If you're storing data that is the same across orgs such as the JWT signing key for the JWT flow then you should use a Protected Custom Metadata record. Best you use one Metadata Type for all of this stuff as Metadata Types have severe limits - 200 per org across all managed an unmanaged code. Nov 8 at 10:01
  • 1
    @Chirag and Richard thank you both for your replies. This is very helpful.
    – Fernando
    Nov 8 at 11:02
2

IMHO You need to create a connected App inside the managed package; better if you use a certificate and enable the Oauth flows. then implement the JWT bearer flow to get a new access token previous (you can use the platform cache to store it for a while) and call the metadata API

Why do you need to do this?

You have created a managed package that is doing metadata changes on the user's behalf (that is because you are using the SessionId). There is no way to track that changes were done by a 3rd party app(your package) with the user's approval. That is the purpose of creating a connected App

6
  • Why must the Connected App be created and packaged in the Managed Package. Can't you just create them in the Partner Business org and use them inside your package? I am referring to this discussion and Best practice to only package the App under certain circumstances as it is less flexible salesforce.stackexchange.com/questions/60784 Nov 7 at 9:13
  • 1
    Creating the Connected App in the PBO is an old practice and a bad practice because the admin doesn't have control over the connected APP, only the ISV.
    – Torsen
    Nov 7 at 14:22
  • 1
    I've prototyped this using the JWT Bearer Flow, but not yet put it into a managed package. I didn't cache the session key but got a new one every time, but then my access is infrequent. It was easy enough, but I do have concerns managing the secret key which becomes a whole new massive threat vector. Especially given I can sign the JWT as any user so switching user context! Though I have reason to suspect interesting things may happen if I tried. Nov 8 at 9:55
  • 1
    The reason I investigated this is because of the Assured Session setting, which prevents asynchronous code using UserInfo.getSessionId() to call into Salesforce. The Connected App was set to never require an assured session, so was able to operate even when the user had Assured Sessions configured. Nov 8 at 9:58
  • 1
    @Torsen I've looked back over my proof of concept code. I use the Auth.JWS constructor to make my token. This needs a Named Certificate. I don't believe it's possible to package named certificates. How did you work around this? Did you manually build your JWT using the Crypto APIs to sign it? Nov 8 at 10:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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