26

Background

I'm using Apex Wrapper Salesforce Metadata API to update some picklist values on the fly. (i.e. Every time a record is inserted/updated/deleted in Table A, I'm adding/subtracting picklist values into a MultiselectePicklist in table B).

Since,

An Apex trigger can execute a callout when the callout is invoked within a method defined as asynchronous: that is, defined with the @future keyword.

I implemented a method with the future annotation, and

For Apex code that is executed asynchronously, such as @future methods, Batch Apex jobs, or scheduled Apex jobs, getSessionId returns null.

so, I needed to pass the session Id as a parameter in the method, I implemented something like:

@future (callout=true)
public static void addContactPicklistPreference(List<String> preferences, String sessionId){
    Schema.DescribeSObjectResult sObjectDescribe = Contact.sObjectType.getDescribe();
    String picklistFieldName = 'Preference__c';              

    Picklist_MDT.addPicklistValues(sObjectDescribe, picklistFieldName, artists, sessionId);
}

public class Picklist_MDT { 
    public static void addPicklistValues(Schema.DescribeSObjectResult sObjectDescribe, String picklistFieldName, List<String> picklistValueNames, String sessionId) {
        MetadataService.MetadataPort service = createService(sessionId);
        ...    
    }

     public static MetadataService.MetadataPort createService(String sessionId) {
        MetadataService.MetadataPort service = new MetadataService.MetadataPort();
        service.SessionHeader = new MetadataService.SessionHeader_element();
        service.SessionHeader.sessionId = sessionId;
        return service;
    }
}

And called the addContactPicklistPreference method from trigger context passing it the UserInfo.getSessionId() value.

Problem

This worked just fine in salesforce classic but not in lightning, when it triggers in lightning I'm getting System.CalloutException: Web service callout failed: WebService returned a SOAP Fault: INVALID_SESSION_ID.... Funny thing is that if I put a System.debug(UserInfo.getSessionId()); before calling the addContactPicklistPreference method it prints a valid session Id in both classic and lightning.

I would really appreciate any input about this.

2
31

So, as it turns outs there is no way of getting a valid API-capable Session Id from lightning component, here is a quote from the partner discussion forums:

Three things:

  1. There's no $Api global variable in Lightning Components today. So, {! $Api.whatever } will always fail.

  2. This is intentional at this time. There's no official/supported way to get an API-capable session ID in Lightning Components. Again, this is by design.

  3. With only a little creativity and cleverness you can create a bare-bones Visualforce page with {! $Api.SessionID }, and then call getContent(thatPage) from Apex to get a page that contains an API-capable session ID, which you should be able to parse out easily enough.

From there, it's not hard to pass that session ID along to Lightning Components via an @AuraEnabled method.

But let's be clear: this is a hack, the performance will be poor, and there's good reasons to keep an API-capable session ID out of your Lightning Components code.

So, I ended-up building the not recommended hack as a workaround for this to work in Lightning.

Just create a simple Visualforce Page as:

<apex:page>
    Start_Of_Session_Id{!$Api.Session_ID}End_Of_Session_Id
</apex:page>

And then get the Session Id with the visualforce_Page.getContent() method, something like:

public static MetadataService.MetadataPort createService()
{
    MetadataService.MetadataPort service = new MetadataService.MetadataPort();
    service.SessionHeader = new MetadataService.SessionHeader_element();
    //service.SessionHeader.sessionId = UserInfo.getSessionId();
    service.SessionHeader.sessionId = Utils.getSessionIdFromVFPage(Page.SessionId);
    return service;
}

Where

global class Utils {
    global static String getSessionIdFromVFPage(PageReference visualforcePage){
        String content = visualforcePage.getContent().toString();
        Integer s = content.indexOf('Start_Of_Session_Id') + 'Start_Of_Session_Id'.length(),
                e = content.indexOf('End_Of_Session_Id');
        return content.substring(s, e);
    }
}

And then you got a valid session id to work with.

6
  • 5
    interesting...amounts to the same, but I created a page like this, makes parsing a bit easier <apex:page contentType="application/json" > { "sessionId": "{!$Api.Session_ID}" } </apex:page> – Phil Hawthorn Jan 9 '17 at 20:08
  • I have same requirement and I am using your trick but I am getting some error Page session id Doesn't exist . cloud you please help me . – Nagaraja SV Jan 19 '17 at 14:48
  • @NagarajaSV I think your org is having Lightning locker service enabled. This approach will not work with locker service enabled. – Jai Chaturvedi Mar 6 '17 at 1:09
  • One way I think might work, but haven't tried. Try firing up the event with session Id as param from vf page and then catch that in lightning component and assign it to an attribute. – Jai Chaturvedi Mar 6 '17 at 1:10
  • Hi @NagarajaSV, do you have 'My Domain' (under Setup - Domain Management - My Domain) enable? This will prevent you from making a callout to the same domain so the MetadataService won't work. – Jose Mar 16 '17 at 21:45
6

According to the known issue Summer'17- Generating a Session Id from Lightning Domain Provides Invalid Session Id (W-3981698) it should be possible to pass UserInfo.getSessionId() out to lightning component via an @AuraEnabled an use it for API calls.

This should be possible from Winter '18 onward.

7
  • I say should here as I've had someone tell me this still isn't working for them. – Daniel Ballinger Mar 20 '18 at 1:44
  • 7
    UserInfo.getSessionId() doesn't work in Summer 18 – Matthew Mitchener Jun 8 '18 at 16:06
  • Can anyone please confirm if this is actually fixed. I am on ApiVersion 45. From lightning component, I am calling controller, which in turn calls the MetadataService class to deploy trigger metadata. In metadata service I am using "UserInfo.getSessionId()" but still getting the same error as mentioned here. Am I missing something? – MandyKool Aug 30 '19 at 21:18
  • 1
    Its still not working as of 47.0 – Mohan P Jan 28 '20 at 21:02
  • Well, in Summer'20 I am getting a value from Apex in Javascript. However, good luck trying to use that session id, as all I get are errors: This session is not valid for use with the REST API. – Sander de Jong Aug 13 '20 at 14:15
1

Note that if your Lightning Component is in a Flow Screen, you can retrieve the SessionId using a Flow formula with $Api.Session_ID and then pass it into the lightning component.

1
0

I know this question is pretty old and has been answered. However, I found a better fix for Apex that doesn't include the Visualforce hack. So I thought I would post it here for others if they come across this.

Simply place all your callouts into an internal wrapper class, making sure to set the sessionID = UserInfo.getUserId() inside the subclass, not from the controller. This apparently removes the user's session ID far enough away from the controller that it gets the correct one for the call. At least this works for me when trying to access the Tooling API.

public class MyController{

  public void getData()
  {
       ToolingClient clt = new ToolingClient();
       ToolingClient.ReturnResult = clt.getmyData();
       //do what you want with the data
  }
  public class ToolingClient{
    public ToolingClient(String strSessionId)
    {
        // set timeout
        this.timeout_x = 60 * 1000;
        //this.SessionHeader.sessionId = strSessionId;

        this.SessionHeader.sessionId = UserInfo.getSessionId();
        this.endpoint_x = RemoteSiteController.getApiUrl() + '/services/Soap/T/48.0';
    }
  //put all the callouts 
   }

}

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