+ Start a Discussion
vikashvikash 

Calling Apex Rest custom web service from Apex code

Hi everyone,

 

    I am a newbie to FDC (so please correct me if I am wrong anywhere) . I have gone thorugh Apex REST API and found that we can create our custom web services in Apex and expose them as REST services using Apex REST. But in most of the references it is given that we can use any programming language of our choice ( i.e. from outside FDC) to invoke this custom WS via REST API. But I want to invoke the same using Apex code (i.e., from inside FDC) via REST API.

 

Any help is highly appreciated

Best Answer chosen by Admin (Salesforce Developers) 
Pat PattersonPat Patterson

You beat me, Simon :-)

 

Vikash - try adding the following line after req.setEndpoint(url);

 

req.setHeader('Authorization', 'OAuth '+UserInfo.getSessionId());

 Simon - it's a bit odd that it would complain about API being disabled, rather than a bad or missing token :-/

All Answers

Pat PattersonPat Patterson

Hi Vikash,

 

I assume that you're trying to call an Apex REST service in one org (let's call it the 'target' org) from another (let's call it the 'source' org), since if the caller and service were the same org, you could just call the function directly :-)

 

Here's some sample code for the target org - it's very simple indeed:

 

@RestResource(urlMapping='/TestRest')
global class TestRest {
    @HttpGet
    global static String getTestRest(RestRequest req, RestResponse res) {
        String name = req.params.get('name');
    
        return 'Hello '+name;
    }
}

It's a little bit more involved for the source org, since the source app needs to authenticate to the target org. All of the following instructions apply to the source org...

 

First, add https://login.salesforce.com/ and your target org instance URL (e.g. https://na1.salesforce.com or https://mycompany.my.salesforce.com) as Remote Sites (Setup | Administration Setup | Security Controls | Remote Site Settings).

 

Next, create a remote access application (Setup | App Setup | Develop | Remote Access) - you can just give it a dummy http://localhost as the callback URL.

 

Now you'll need to copy JSONObject from http://code.google.com/p/apex-library/source/browse/trunk/JSONObject/src/unpackaged/classes/JSONObject.cls?r=14 to a new Apex Class in your source org.

 

Finally, here's the sample code for the source org:

 

public class RestTest {
    private static JSONObject oauthLogin(String loginUri, String clientId,
        String clientSecret, String username, String password) {
        HttpRequest req = new HttpRequest(); 
 
        req.setMethod('POST');
        req.setEndpoint(loginUri+'/services/oauth2/token');
        req.setBody('grant_type=password' +
            '&client_id=' + clientId +
            '&client_secret=' + clientSecret +
            '&username=' + EncodingUtil.urlEncode(username, 'UTF-8') +
            '&password=' + EncodingUtil.urlEncode(password, 'UTF-8'));
    
        Http http = new Http();
  
        HTTPResponse res = http.send(req);

        System.debug('BODY: '+res.getBody());
        System.debug('STATUS:'+res.getStatus());
        System.debug('STATUS_CODE:'+res.getStatusCode());
        
        return new JSONObject(res.getBody());
    }
    
    public static String restTest(String name) {
        // use https://test.salesforce.com for sandbox orgs
        JSONObject oauth = oauthLogin('https://login.salesforce.com', 
            'YOUR_REMOTE_ACCESS_APP_CONSUMER_KEY',
            'YOUR_REMOTE_ACCESS_APP_CONSUMER_SECRET', 
            'USERNAME_IN_TARGET_ORG', 
            'PASSWORD_IN_TARGET_ORG');
            
        String accessToken = oauth.getValue('access_token').str,
               instanceUrl = oauth.getValue('instance_url').str;

        HttpRequest req = new HttpRequest(); 
 
        req.setMethod('GET');
        req.setEndpoint(instanceUrl+'/services/apexrest/TestRest?name='+name);
        req.setHeader('Authorization', 'OAuth '+accessToken);

        Http http = new Http();
  
        HTTPResponse res = http.send(req);

        System.debug('BODY: '+res.getBody());
        System.debug('STATUS:'+res.getStatus());
        System.debug('STATUS_CODE:'+res.getStatusCode());
        
        return res.getBody();
    }
}

You will need to insert your remote access app consumer key/secret and target org username/password in the appropriate places in the restTest() method.

 

What that source app code does is to authenticate to the target org via OAuth username/password flow, then call the REST service and return the result. If you execute RestTest.restTest('Vikash'); in the system log, you should see Hello Vikash in the debug output.

vikashvikash

Hi Pat,

 

 I am very much thankful for your help, I am giving you my code so that you can get better Idea of wat I am doing

 

My target org code: 

 

@RestResource(urlMapping='/GetService/*')  
global with sharing class getservice1
{      
     @HttpGet     
     global static String postRestMethod(RestRequest req, RestResponse res)     
     {          
          return 'Hi, You have invoked getservice1 created using Apex Rest and exposed using REST API';        
     }
}

 and my source org code is:

 

<apex:page controller="getservice2">
    <apex:form >
        <apex:commandButton action="{!getResult}" value="Submit"> </apex:commandButton>
        <br/><br/>        {!myresponse}
    </apex:form>
</apex:page>

 

public class getservice2
{
    public String myresponse{get;set;}
    public Pagereference getResult()
    {
        HttpRequest req = new HttpRequest();
        Http http = new Http();
        req.setMethod('GET');
        String url = 'https://vikashtalanki-developer-edition.na12.force.com/services/apexrest/GetService';
        req.setEndpoint(url);
        HTTPResponse resp = http.send(req);
        myresponse=resp.getBody();          

        return null;    }
}

 which on cliking submit button should give me 'Hi, You have invoked getservice1 created using Apex Rest and exposed using REST API'..........

Instead of which I am getting '[{"message":"API is disabled for this User","errorCode":"API_CURRENTLY_DISABLED"}]'

 

I have already added 'https://vikashtalanki-developer-edition.na12.force.com/services/apexrest/GetService'  in Remote Sites (Setup | Administration Setup | Security Controls | Remote Site Settings).

 

Plz correct me if there is  any flaw/mistake/wrong in the way I am doing this???

 

 

dkadordkador

Does your user in the source org have API access?  The error indicates that it does not.

Pat PattersonPat Patterson

Hi Vikash,

 

As Daniel says, the error message gives the clue - check that your user has API access.

 

Are you using one org here, or two?

 

Cheers,

 

Pat

vikashvikash

Hi,

 

 

     Yes I already have the API enabled and I am using same target and source org for this example. Since I am using a GET method , It should work if I open https://vikashtalanki-developer-edition.na12.force.com/services/apexrest/GetService is a browser but am getting a 403 error when I do this.

SuperfellSuperfell

You are not sending an Authorization: OAuth {someToken} header to authenticate your request.

vikashvikash

Yes I am  not sending an Authorization: OAuth {someToken} header to authenticate my request. But is that neccessary???

 
Pat PattersonPat Patterson

You beat me, Simon :-)

 

Vikash - try adding the following line after req.setEndpoint(url);

 

req.setHeader('Authorization', 'OAuth '+UserInfo.getSessionId());

 Simon - it's a bit odd that it would complain about API being disabled, rather than a bad or missing token :-/

This was selected as the best answer
Pat PattersonPat Patterson

Yes - it's very necessary - all calls to the API [1] must carry a token.

 

[1] Well, nearly all - the 'root' /services/data call doesn't. You can call it without a token and it will tell you what API versions the REST service supports:

 

$ curl https://na1.salesforce.com/services/data -H 'X-PrettyPrint: 1'
[ {
  "label" : "Winter '11",
  "version" : "20.0",
  "url" : "/services/data/v20.0"
}, {
  "label" : "Spring '11",
  "version" : "21.0",
  "url" : "/services/data/v21.0"
}, {
  "label" : "Summer '11",
  "version" : "22.0",
  "url" : "/services/data/v22.0"
} ]

 

vikashvikash

Take a bow Pat, Awesome , it worksssssssssssssssssssssssssssssssssssssss.................. Can you just explain me about this a little bit???

Pat PattersonPat Patterson

Sure - as I mentioned, every API call must have a token representing an authenticated user. The REST API accepts an OAuth token (pretty much a session ID) in the 'Authorization' HTTP header (see http://wiki.developerforce.com/index.php/Getting_Started_with_the_Force.com_REST_API for more detail). In Apex Code, UserInfo.getSessionId() returns you a session ID that you can then add as a header to your outbound call.

 

Now... Why are you using the REST API to call into your own org? Why not just call getservice1.postRestMethod()?

 

BTW - this mechanism WILL NOT WORK for calls from one org to another. For that you will need to obtain a token valid for the target org, which is much more tricky. I posted a username/password mechanism earlier in this thread. You can also do OAuth web server flow (see wiki.developerforce.com/index.php/Digging_Deeper_into_OAuth_2.0_on_Force.com) with code similar to https://github.com/metadaddy-sfdc/Force.com-Toolkit-for-Facebook/blob/master/src/classes/FacebookLoginController.cls - actually, that's code to do OAuth against Facebook, but the principle is the same :-)

vikashvikash

Yeah Pat, when I try to call it from another org I am getting the following error

 

System.CalloutException: Unauthorized endpoint, please check Setup->Security->Remote site settings. endpoint = https://vikashtalanki-developer-edition.na12.force.com/services/apexrest/GetService?name=vikash

 

Class.getservice2.getNameMethod: line 19, column 29 Class.getservice2.submit: line 8, column 17 External entry point

 

Can you tell what to add in my code to get it authenticated

 

SuperfellSuperfell

As the error message indicates, you need login to the web app, goto setup -> security -> remote sites and add new remote site for that url.

vikashvikash

It has already been added in the remote site settings, I am worrying about authenticated code for my web app when I access it from another org

Pat PattersonPat Patterson

You have to add the target org host to the remote sites in the source org. You might have it the other way round...

vikashvikash

I have added 'https://vikashtalanki-developer-edition.na12.force.com' as target host in remote site settings, but I am getting the error as  " [{"message":"Could not find a match for URL /GetService","errorCode":"NOT_FOUND"}]".

Pat PattersonPat Patterson

Which end did you move to your new org - source or target?

vikashvikash

source

Pat PattersonPat Patterson

Hmm - not sure. If you've made no other changes, you're passing a source org session ID to the target org in the Authentication header, you're confusing the hell out of things. I'm not even sure web server OAuth flow is going to work here, since all the cookies may end up in the same domain. Let me try it out on my DE org(s) and get back to you...

vikashvikash

I apologise for messing up things.

 

This code is working fine if both source and target are in same org.

 

Target :

@RestResource(urlMapping='/GetService/*')  
global with sharing class getservice1
{  
    @HttpGet 
    global static String getRestMethod(RestRequest req, RestResponse res) 
    {  
        String name = req.params.get('name');
        return 'Hello '+name+', you have just invoked a custom Apex REST web service exposed using REST API' ;
    }
}

Source:

<apex:page controller="getservice2">
    <apex:form >
        <apex:pageBlock >

        <apex:pageBlockSection >
               <apex:pageBlockSectionItem >
                    <apex:outputLabel for="name">Name</apex:outputLabel>
                    <apex:inputText id="name" value="{!name}"/>
               </apex:pageBlockSectionItem>
          </apex:pageBlockSection>
        </apex:pageBlock>

        <apex:commandButton action="{!submit}" value="Submit"> </apex:commandButton>
        <br/><br/>        {!getname}
    </apex:form>
</apex:page>

public class getservice2
{
    public String getname {get;set;}
    public String name{get;set;}
    public PageReference submit() 
    {
        getname=getNameMethod(name);
        return null;
    }
    public static String getNameMethod(String name)
    {
        HttpRequest req = new HttpRequest();
        Http http = new Http();
        req.setMethod('GET');
        String url = 'https://vikashtalanki-developer-edition.na12.force.com/services/apexrest/GetService?name='+name;        
        req.setEndpoint(url);
        req.setHeader('Authorization', 'OAuth '+UserInfo.getSessionId());
        HTTPResponse resp = http.send(req);
        return resp.getBody();
     }
}

 I need to know what the neccessary changes to be made in above code if the source is moved to another org

Pat PattersonPat Patterson

I just got this working. It's pretty tricky, because you have to get a token for a user in the target org. You can't do this using login.salesforce.com, since you'll likely just get a token for your source org, which won't work.

So... Step 1 - you need to set up 'My Domain' in the target org - Setup | Administration Setup | Company Profile | My Domain. Note that this takes a few hours to take effect. You'll end up with a domain name of the form yourdomain-developer-edition.my.salesforce.com. This is the key to getting org->org OAuth working - you need two distinct domain names.

Step 2. Here is the code for the source org. The target org is unchanged (apart from setting up My Domain):

Controller

/**
 * @author Pat Patterson - ppatterson@salesforce.com
 */

public abstract class OAuthRestController {
    static String clientId = 'YOUR_CLIENT_ID'; // Set this in step 3
    static String clientSecret = 'YOUR_CLIENT_SECRET'; // Set this in step 3
    static String redirectUri = 'https://c.na12.visual.force.com/apex/RestCall'; // YOUR PAGE URL IN THE SOURCE ORG
    static String loginUrl = 'https://yourdomain-developer-edition.my.salesforce.com'; // YOUR MY DOMAIN URL IN THE TARGET ORG
    static String cookieName = 'oauth';

    public PageReference login() {
        // Get a URL for the page without any query params
        String url = ApexPages.currentPage().getUrl().split('\\?')[0];
       
        System.debug('url is '+url);

        String oauth = (ApexPages.currentPage().getCookies().get(cookieName) != null ) ?
            ApexPages.currentPage().getCookies().get(cookieName).getValue() : null;
        if (oauth != null) {
            // TODO - Check for expired token
        }
       
        System.debug('oauth='+oauth);
        if (oauth != null) {
            // All done
            return null;
        }
       
        // If we get here we have no token
        PageReference pageRef;
       
        if (! ApexPages.currentPage().getParameters().containsKey('code')) {
            // Initial step of OAuth - redirect to OAuth service
            System.debug('OAuth Step 1');
       
            String authuri = loginUrl+'/services/oauth2/authorize?'+
                'response_type=code&client_id='+clientId+'&redirect_uri='+redirectUri;
                           
            pageRef = new PageReference(authuri);
        } else {
            // Second step of OAuth - get token from OAuth service
            String code = ApexPages.currentPage().getParameters().get('code');

            System.debug('OAuth Step 2 - code:'+code);
               
            String body = 'grant_type=authorization_code&client_id='+clientId+
                '&redirect_uri='+redirectUri+'&client_secret='+clientSecret+
                '&code='+code;
            System.debug('body is:'+body);
               
            HttpRequest req = new HttpRequest();
            req.setEndpoint(loginUrl+'/services/oauth2/token');
            req.setMethod('POST');
            req.setBody(body);
       
            Http h = new Http();
            HttpResponse res = h.send(req);
   
            String resp = res.getBody();
            System.debug('FINAL RESP IS:'+EncodingUtil.urlDecode(resp, 'UTF-8'));
           
            ApexPages.currentPage().setCookies(new Cookie[]{new Cookie(cookieName,
                res.getBody(), null,-1,false)});
               
            // Come back to this page without the code param
            // This makes things simpler later if you end up doing DML here to
// save the token somewhere
 pageRef = new PageReference(url); pageRef.setRedirect(true); } return pageRef; } public static String getRestTest() { String oauth = (ApexPages.currentPage().getCookies().get(cookieName) != null ) ? ApexPages.currentPage().getCookies().get(cookieName).getValue() : null; JSONObject oauthObj = new JSONObject( new JSONObject.JSONTokener(oauth)); String accessToken = oauthObj.getValue('access_token').str, instanceUrl = oauthObj.getValue('instance_url').str; HttpRequest req = new HttpRequest(); req.setMethod('GET'); req.setEndpoint(instanceUrl+'/services/apexrest/superpat/TestRest?name=Pat'); req.setHeader('Authorization', 'OAuth '+accessToken); Http http = new Http(); HTTPResponse res = http.send(req); System.debug('BODY: '+res.getBody()); System.debug('STATUS:'+res.getStatus()); System.debug('STATUS_CODE:'+res.getStatusCode()); return res.getBody(); } }

 

Page - I called it 'RestCall'

 

<apex:page controller="OAuthRestController" action="{!login}">
  <h1>Rest Test</h1>
  getRestTest says "{!restTest}"
</apex:page>

 

Step 3. Create a remote access application (Setup | App Setup | Develop | Remote Access) in the source org with your Visualforce page URL (e.g. https://c.na12.visual.force.com/apex/RestCall) as the callback URL. Edit the controller to set the client ID and client secret values (consumer key and consumer secret in the remote access app page).

 

Now when you go to the page, you may be prompted to log in (depending on whether or not you have a session in the target org), then you should be prompted to allow the remote access app to access your data. After that, the REST call should take place and you'll see the result :-)

 

The code is a little horrible, since it puts the whole oauth response in a cookie, but it works :-) A better solution is to generate a random string for the cookie and save the oauth response in a custom object record, indexed by the cookie. You can see how that works in the latest Facebook toolkit code at https://github.com/metadaddy-sfdc/Force.com-Toolkit-for-Facebook - look at FacebookObject.cls.

Pat PattersonPat Patterson

BTW - it works equally well with a 'site' at the source org - then you only have to login to the target org.

vikashvikash

 Hi Pat,

 

 I am trying the way you showed but unable to get the web page for logging in and it shows the error as http 400 bad request. I am totally stuck at this point

 

My code goes as below:

 

<apex:page controller="OAuthRestController" showHeader="false" action="{!login}">  
 <apex:form >
            <apex:pageBlock >
        <apex:pageBlockSection >
               <apex:pageBlockSectionItem >
                    <apex:outputLabel for="name">Name</apex:outputLabel>
                    <apex:inputText id="name" value="{!name}"/>
               </apex:pageBlockSectionItem>
          </apex:pageBlockSection>
        </apex:pageBlock>

        <apex:commandButton action="{!submit}" value="Submit"> </apex:commandButton>
        <br/><br/>        {!getname}
    </apex:form>
</apex:page>


public abstract class OAuthRestController 
{
    static String clientId = '3MVG9QDx8IX8nP5S2sK3mCS5MVzYxYQl7KLLb6Ye_eu9B_mVlbcsGaXsOtcJqYK_UZAkiNLc6lidr2UlVfeTu'; // Set this in step 3
    static String clientSecret = '7603342980775353272' ; // Set this in step 3
    //static String redirectUri = 'https://c.na12.visual.force.com/apex/restservicepage'; 
    static String redirectUri='https://sandeepreddy-developer-edition--c.na12.visual.force.com/apex/restservicepage';// YOUR PAGE URL IN THE SOURCE ORG
    static String loginUrl = 'https://vikashtalanki-developer-edition.my.salesforce.com'; // YOUR MY DOMAIN URL IN THE TARGET ORG
    static String cookieName = 'oauth';
     public PageReference login() {
        // Get a URL for the page without any query params
        String url = ApexPages.currentPage().getUrl().split('\\?')[0];
       
        System.debug('url is '+url);

        String oauth = (ApexPages.currentPage().getCookies().get(cookieName) != null ) ?
            ApexPages.currentPage().getCookies().get(cookieName).getValue() : null;
        if (oauth != null) {
            // TODO - Check for expired token
        }
       
        System.debug('oauth='+oauth);
        if (oauth != null) {
            // All done
            return null;
        }
       
        // If we get here we have no token
        PageReference pageRef;
           Map<String,String> mymap=ApexPages.currentPage().getParameters();
        if (!(mymap.containsKey('code'))) {
            // Initial step of OAuth - redirect to OAuth service
            System.debug('OAuth Step 1');
       
            String authuri = loginUrl+'/services/oauth2/authorize?'+
                'response_type=code&client_id='+clientId+'&redirec​t_uri='+redirectUri;
                           
            pageRef = new PageReference(authuri);
        } else {
            // Second step of OAuth - get token from OAuth service
            String code = ApexPages.currentPage().getParameters().get('code');

            System.debug('OAuth Step 2 - code:'+code);
               
            String body = 'grant_type=authorization_code&client_id='+clientId+
                '&redirect_uri='+redirectUri+'&client_secret='+clientSecret+
                '&code='+code;
            System.debug('body is:'+body);
               
            HttpRequest req = new HttpRequest();
            req.setEndpoint(loginUrl+'/services/oauth2/token');
            req.setMethod('POST');
            req.setBody(body);
       
            Http h = new Http();
            HttpResponse res = h.send(req);
   
            String resp = res.getBody();
            System.debug('FINAL RESP IS:'+EncodingUtil.urlDecode(resp, 'UTF-8'));
           
            ApexPages.currentPage().setCookies(new Cookie[]{new Cookie(cookieName,
                res.getBody(), null,-1,false)});
               
            // Come back to this page without the code param
            // This makes things simpler later if you end up doing DML here to            // save the token somewhere            pageRef = new PageReference(url);
            pageRef.setRedirect(true);
        }
       
        return pageRef;
    }
   
    /*public static String getRestTest() {
        String oauth = (ApexPages.currentPage().getCookies().get(cookieName) != null ) ?
            ApexPages.currentPage().getCookies().get(cookieName).getValue() : null;
        JSONObject oauthObj = new JSONObject( new JSONObject.JSONTokener(oauth));
           
        String accessToken = oauthObj.getValue('access_token').str,
               instanceUrl = oauthObj.getValue('instance_url').str;

        HttpRequest req = new HttpRequest();
 
        req.setMethod('GET');
        req.setEndpoint(instanceUrl+'/services/apexrest/su​perpat/TestRest?name=Pat');
        req.setHeader('Authorization', 'OAuth '+accessToken);

        Http http = new Http();
 
        HTTPResponse res = http.send(req);

        System.debug('BODY: '+res.getBody());
        System.debug('STATUS:'+res.getStatus());
        System.debug('STATUS_CODE:'+res.getStatusCode());
       
        return res.getBody();
    }*/
     public String getname {get;set;}
    public String name{get;set;}
    public PageReference submit() 
    {
        getname=getNameMethod(name);
        return null;
    }
    public static String getNameMethod(String name)
    {
        HttpRequest req = new HttpRequest();
        Http http = new Http();
        req.setMethod('GET');
        String url = 'https://vikashtalanki-developer-edition.my.salesforce.com/services/apexrest/RestService?name='+name;        
        req.setEndpoint(url);
        req.setHeader('Authorization', 'OAuth '+UserInfo.getSessionId());
        HTTPResponse resp = http.send(req);
        //myresponse=resp.getBody();          
        return resp.getBody();
        //return null;    
    }

}

 

Pat PattersonPat Patterson

Hi Vikash,

 

You are still using UserInfo.getSessionId() - this won't work when you call between orgs - you have to use a token from the target org - the login() method gets this and puts it in a cookie.

 

Your getNameMethod should look like this:

 

    public static String getNameMethod(String name)
    {
        String oauth = (ApexPages.currentPage().getCookies().get(cookieName) != null ) ?
            ApexPages.currentPage().getCookies().get(cookieName).getValue() : null;
        JSONObject oauthObj = new JSONObject( new JSONObject.JSONTokener(oauth));
           
        String accessToken = oauthObj.getValue('access_token').str,
               instanceUrl = oauthObj.getValue('instance_url').str;

        HttpRequest req = new HttpRequest();
 
        req.setMethod('GET');
        String url = 'https://vikashtalanki-developer-edition.my.salesforce.com/services/apexrest/RestService?name='+name;        
        req.setEndpoint(url);
        req.setHeader('Authorization', 'OAuth '+accessToken);

        Http http = new Http();
 
        HTTPResponse res = http.send(req);

        System.debug('BODY: '+res.getBody());
        System.debug('STATUS:'+res.getStatus());
        System.debug('STATUS_CODE:'+res.getStatusCode());
       
        return res.getBody();
    }

 Cheers,

 

Pat

vikashvikash

Hi Pat,

 

 I have made the neccessary changes in getNameMethod. But according to the code, as soon as the visual page loads, it should redirect me to https://vikashtalanki-developer-edition.my.salesforce.com where i can give my credentials. But I am geeting HTTP 400 Bad Request and the URL was https://vikashtalanki-developer-edition.my.salesforce.com/services/oauth2/authorize?client_id=3MVG9QDx8IX8nP5Qzkd59wBVVRJOGUaJhMDBir6pY11AnG1s4FHjSH0fMt1.9.yZPFGfCGnqPFrhQPfIK0mFB&redirec%E2%80%8Bt_uri=https%3A%2F%2Fc.na12.visual.force.com%2Fapex%2Frestservicepage&response_type=code 

vikashvikash

Hi Pat,

 

      The Error it sowing is "redirect url mismatch", but as far as I know , we have followed your code correctly, Really stuck at this point

Pat PattersonPat Patterson

Hi Vikash,

 

Double check that the callback URL you set in the remote access app is identical to the one in your code - https://sandeepreddy-developer-edition--c.na12.visual.force.com/apex/restservicepage

 

Also - I notice some corruption in the URL you posted - 'redirec%E2%80%8Bt_uri' should be 'redirect_uri'. Also, in the source, you seem to have some corruption in the highlighted line:

 

        if (!(mymap.containsKey('code'))) {
            // Initial step of OAuth - redirect to OAuth service
            System.debug('OAuth Step 1');
       
 String authuri = loginUrl+'/services/oauth2/authorize?'+ 'response_type=code&client_id='+clientId+'&redirec​t_uri='+redirectUri;
                           
            pageRef = new PageReference(authuri);
        } else {

That '&redirect_uri' seems to have some non-printable characters between '&redirec' and 't_uri'. If you retype that string, and make sure the URLs match, it should work.

 

Cheers,

 

Pat

vikashvikash

Hi Pat,

 

     The error is exaclty what you said, now its working very fine. We are done with it. Im very much thankful to you, you really helped us alot, Can you just tell me whether this OAuth authentication is one time authentication or not? Because when I open the page for the first time it is going to loginUrl as expected, & on subsequent access of that page , its directly giving the visual force page. But when I cloase the browser window and open again, it again taking me to loginUrl page, So I think this is not one time authentication, Can you guide about this plz

Pat PattersonPat Patterson

The authentication is session-based, just like logging in to login.salesforce.com. If you keep your browser open, you can keep going to pages within salesforce.com until your session expires (by default, I think this is 2 hours). Similarly for this integration here.

 

By the way - the code I posted is 'proof of concept' - it would need some tidying up for production. In particular, there is no error handling. To be robust, you will need to handle a 401 response (invalid or expired token) for the REST call. If you get a 401, you need to repeat the login process to get a new access token.

vikashvikash

Hi Pat,

 

    I guess, requesting for new token, if one expires can be done using request_token that is generated in response code. Can you plese tell me when and why  does a token expire? If we close the browser , then the token expires. Can the token expire before the session gets finished??

Pat PattersonPat Patterson

Hi Vikash,

 

Access tokens follow the same rules as regular Salesforce session IDs - they have an idle timeout that is set in Setup | Administration Setup | Session Settings | Session Timeout. The default session timeout is 2 hours, but an org admin can set it to any of a variety of values from 15 minutes to 8 hours. See https://login.salesforce.com/help/doc/user_ed.jsp?loc=help&target=admin_sessions.htm&section=Security for more details.

 

Cheers,

 

Pat

vikashvikash

Hi Pat,

 

    I have a small idea, If I store the access token in a database at source org and use that for further communications btw source & target, do I still need to authorize when I access the target webservice from another machine? I mean, can the same accesstoken be used(until it doesnt get expired) from different machines to invoke target service??

 

Suppose user A logged into machine A with his own credentials and invoked the target web service ans as a result got accesstoken. Now can this accesstoken be used by another/same user on different machine, say machine B???

 

My intention is to authorize the target webservice only once(that is for the first time) and use that accesstoken from anywhere with out any need to authorize again.

Pat PattersonPat Patterson

Hi Vikash,

 

Yes, I think that would work - token expiration is your only problem.

 

You know, most of this stuff, you can just try it out quicker than writing a question here, and certainly quicker than waiting for a reply :smileywink:

 

Cheers,


Pat

Pat PattersonPat Patterson

Hi Vikash,

 

I've just been reminded that you receive a long-lived 'refresh token' as part of the response in the web server flow. You can store this persistently and use it to obtain a new access token when the old one expires. See my article for a deeper discussion of refresh tokens and their use.

 

Cheers,


Pat

vikashvikash

Hi Pat,

 

          As far as I know, storing the accesstoken in the database is not a better solution as far as security is concerned. Is there any way to implement this using OpenSSO??

Pat PattersonPat Patterson

Hi Vikash,

 

You would store the refresh token (not the access token) in the database for a given user. Why would this not be secure? You can lock security down so it is only accessible to the relevant user.

 

I don't think OpenSSO really helps, since you have one salesforce org calling another.

 

Cheers,

 

Pat

vikashvikash

But is it not accessable even to administrators of salesforce???  I am looking for a very secured way of implementing OAuth(using this token) & also not making the user to give the credentials of target again & again(this is why I have stored it in db) 

vikashvikash

Hi Pat,

 

       I am trying to explain my requirement in a better way here.................

 

As explained here , I have installed OpenAM in my machine which is acting as a identity provider. when I give the credentials to log into OpenAM , using SAML assertion I am able to login to salesforce without again giving Salesforce credentials. Now using the same SAML assertion(or token) I wanna invoke the service of target org from source org. The reason for this is , if I store the accesstoken in the cookie , it gets invalid once I close the browser. If I store accesstoken in db , It can be compromised(I am not so happy with this even if its safe).............. So I think this can be done in 2 ways(may be both are not possible)

 

1. Use this SAML assertion as accesstoken to invoke target org from source using Oauth (this may not possible as accesstoken is being given by authorizaton server)

2.  Any other way to use this SAML assertion as token with out using OAuth concept.

 

 

I need your ideas on this &

Pat, your guidance is really putting me in the driving seat........... I am really thankful to you,

Pat PattersonPat Patterson

Hi Vikash,

 

If you have a SAML assertion issued by an IdP that your Salesforce org trusts, your app can exchange it for an OAuth token - see https://na1.salesforce.com/help/doc/en/remoteaccess_oauth_web_sso_flow.htm

 

Cheers,


Pat

tggagnetggagne

"I assume that you're trying to call an Apex REST service in one org from another since if the caller and service were the same org, you could just call the function directly"

 

Not true.  Salesforce is exposing functionality via its REST API (like chatter @mentions) that are not available from within Apex.

 

In my client's case, this is exactly what we'd like to do (that along with some reporting stuffs).

Pat PattersonPat Patterson

tggagne - you're right, and in that case (calling REST APIs in the same org) it's very straightforward - see http://blogs.developerforce.com/developer-relations/2011/03/calling-the-rest-api-from-javascript-in-visualforce-pages.html

 

tggagnetggagne

I'd found that earlier, but I don't want to call it from Javascript.  I want to talk to my own org from Apex.

Pat PattersonPat Patterson

tggagne - that is possible - vikash posted code to do exactly that earlier in this thread - http://boards.developerforce.com/t5/REST-API-Integration/Calling-Apex-Rest-custom-web-service-from-Apex-code/m-p/328359#M884

sarvesh001sarvesh001

Hi Vikas,

how  did you start writing sourchis  code , can you write this one in apexclasses.

and how did you got  source code url.

 

Please give me stepwise information

 

Thanks,

Sarvesh.

bansal1.392449029236575E12bansal1.392449029236575E12
i have the access token but still it is showing a 401 unauthorized status when i m calling a httppost method here is my code
HttpRequest req = new HttpRequest();
        HttpResponse res = new HttpResponse();
        Http http = new Http();
         //String endPoint1 = instanceURL + '/services/apexrest/Insurance';
        req.setEndpoint('https://na15.salesforce.com/services/apexrest/Insurance');
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json; charset=UTF-8');
        req.setHeader('Accept', 'application/json');
        req.setHeader('Authorization', 'OAuth'+sessionId);
        req.setBody('{"Name" : "New1"}');

Please let me know if i m missing something or not.
jansi vedhamuthujansi vedhamuthu
hii,
this is my exception
Visualforce ErrorHelp for this Page
System.CalloutException: Invalid HTTP method: post
Error is in expression '{!getResult}' in component <apex:commandButton> in page wq: Class.getservice21.getResult: line 19, column 1
Class.getservice21.getResult: line 19, column 1

 
jansi vedhamuthujansi vedhamuthu
can anyone solve this,
thanks in advance.
 
SFDC 2017SFDC 2017
Hi 
@Pat Patterson,

I used your code for connecting one salesforce Org to another using Connected App.

My code as Follows:

public class RestTest {

    public String restTest { get; set; }
    private static JSONObject oauthLogin(String loginUri, String clientId,
        String clientSecret, String username, String password) {
        HttpRequest req = new HttpRequest(); 
 
        req.setMethod('GET');
        req.setEndpoint(loginUri+'/services/oauth2/token');
        req.setBody('grant_type=password' +
            '&client_id=' + clientId +
            '&client_secret=' + clientSecret +
            '&username=' + EncodingUtil.urlEncode(username, 'UTF-8') +
            '&password=' + EncodingUtil.urlEncode(password, 'UTF-8'));
    
        Http http = new Http();
  
        HTTPResponse res = http.send(req);
        System.Debug('*****************************'+res);
        System.debug('BODY: '+res.getBody());
        System.debug('STATUS:'+res.getStatus());
        System.debug('STATUS_CODE:'+res.getStatusCode());
        
        return new JSONObject(res.getBody());
    }
    
    public static String restTest(String name) {
             JSONObject oauth = oauthLogin('<a href= "https://login.salesforce.com" target="_blank">https://login.salesforce.com</a>',
            '3MVG9Y6d_Btp4xp6GjWvM6Vqowzgn3PxnMwVPSeWRuVClTo..VJYYOIeHxVdyMpoeDeGeDC8MzTFwaPSLBoAU',
            '4075257097069349419', 
            'abc@xyz.demo', 
            '*****');
            
        String accessToken = oauth.getValue('access_token').str,
               instanceUrl = oauth.getValue('instance_url').str;

        HttpRequest req = new HttpRequest(); 
 
        req.setMethod('GET');
        req.setEndpoint(instanceUrl+'/services/apexrest/Account35?name='+name);
        req.setHeader('Authorization', 'OAuth '+accessToken);

        Http http = new Http();
  
        HTTPResponse res = http.send(req);

        System.debug('BODY: '+res.getBody());
        System.debug('STATUS:'+res.getStatus());
        System.debug('STATUS_CODE:'+res.getStatusCode());
        
        return res.getBody();
    }
}


I am getting error Like:

System.CalloutException: no protocol: <a href= "https://login.salesforce.com"; target="_blank">https://login.salesforce.com</a>/services/oauth2/token
Class.prabhaks.RestTest.oauthLogin: line 18, column 1
Class.prabhaks.RestTest.restTest: line 28, column 1

How to Resolve this..

Any one can help .

Thanks in advance...

 
Accelerize DemoAccelerize Demo
HI Pat the example you given seems to be not working
Akash DalviAkash Dalvi
Hi Pat, I'm trying to execute the example you've explained above.I've copy and pasted the above source code, target code and page code in my org.I've also created one connected app in my org and set the callback URL in it which is "https://c.ap1.visual.force.com/apex/GetService"
I've also added my domain name "https://akashdalvi-dev-ed.my.salesforce.com" in Remote site setting.I'm getting "URL No Longer Exists" error whenever I'm executing the VF page.Please find the below codes.
Target code :
@RestResource(urlMapping='/GetService/*')  
global with sharing class getservice1
{  
    @HttpGet 
    global static String getRestMethod() 
    {   
        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;
        String name = req.params.get('name');
        return 'Hello '+name+', you have just invoked a custom Apex REST web service exposed using REST API' ;
    }
}
Source Code :
public abstract class OAuthRestController {
    static String clientId = '3MVG9Y6d_Btp4xp7FzYaVu5vF77qEk0y_iGvHzdmXyk71gTL75PCoi5rOmM7f_kMqP9THR3Y7iXsRl9i_DwZg'; // Set this in step 3
    static String clientSecret = '4692916739247047542'; // Set this in step 3
    static String redirectUri = '<a href="https://c.ap1.visual.force.com/apex/GetService" target="_blank" rel="nofollow">https://c.ap1.visual.force.com/apex/GetService</a>'; // YOUR PAGE URL IN THE SOURCE ORG
    static String loginUrl = '<a href="https://akashdalvi-dev-ed.my.salesforce.com" target="_blank" rel="nofollow">https://akashdalvi-dev-ed.my.salesforce.com</a>'; // YOUR MY DOMAIN URL IN THE TARGET ORG
    static String cookieName = 'oauth';

    public PageReference login() {
        // Get a URL for the page without any query params
        String url = ApexPages.currentPage().getUrl().split('\\?')[0];
       
        System.debug('url is '+url);

        String oauth = (ApexPages.currentPage().getCookies().get(cookieName) != null ) ?
            ApexPages.currentPage().getCookies().get(cookieName).getValue() : null;
        if (oauth != null) {
            // TODO - Check for expired token
        }
       
        System.debug('oauth='+oauth);
        if (oauth != null) {
            // All done
            return null;
        }
       
        // If we get here we have no token
        PageReference pageRef;
       
        if (! ApexPages.currentPage().getParameters().containsKey('code')) {
            // Initial step of OAuth - redirect to OAuth service
            System.debug('OAuth Step 1');
       
            String authuri = loginUrl+'/services/oauth2/authorize?'+
                'response_type=code&client_id='+clientId+'&redirect_uri='+redirectUri;
                           
            pageRef = new PageReference(authuri);
        } else {
            // Second step of OAuth - get token from OAuth service
            String code = ApexPages.currentPage().getParameters().get('code');

            System.debug('OAuth Step 2 - code:'+code);
               
            String body = 'grant_type=authorization_code&client_id='+clientId+
                '&redirect_uri='+redirectUri+'&client_secret='+clientSecret+
                '&code='+code;
            System.debug('body is:'+body);
               
            HttpRequest req = new HttpRequest();
            req.setEndpoint(loginUrl+'/services/oauth2/token');
            req.setMethod('POST');
            req.setBody(body);
       
            Http h = new Http();
            HttpResponse res = h.send(req);
   
            String resp = res.getBody();
            System.debug('FINAL RESP IS:'+EncodingUtil.urlDecode(resp, 'UTF-8'));
           
            ApexPages.currentPage().setCookies(new Cookie[]{new Cookie(cookieName,
                res.getBody(), null,-1,false)});
               
            // Come back to this page without the code param
            // This makes things simpler later if you end up doing DML here to<br>            // save the token somewhere<br>            pageRef = new PageReference(url);
            pageRef.setRedirect(true);
        }
       
        return pageRef;
    }
       public static String getRestTest() {
        String oauth = (ApexPages.currentPage().getCookies().get(cookieName) != null ) ?
            ApexPages.currentPage().getCookies().get(cookieName).getValue() : null;
        JSONObject oauthObj = new JSONObject( new JSONObject.JSONTokener(oauth));
           
        String accessToken = oauthObj.getValue('access_token').str,
               instanceUrl = oauthObj.getValue('instance_url').str;

        HttpRequest req = new HttpRequest();
 
        req.setMethod('GET');
        req.setEndpoint(instanceUrl+'/services/apexrest/superpat/TestRest?name=Pat');
        req.setHeader('Authorization', 'OAuth '+accessToken);

        Http http = new Http();
 
        HTTPResponse res = http.send(req);

        System.debug('BODY: '+res.getBody());
        System.debug('STATUS:'+res.getStatus());
        System.debug('STATUS_CODE:'+res.getStatusCode());
       
        return res.getBody();
    }
}
Page :
<apex:page controller="OAuthRestController" action="{!login}">
  <h1>Rest Test</h1>
  getRestTest says "{!restTest}"
</apex:page>

Please share your valuable thoughts and kindly reply me, what went wrong and how would I fix it?
 
Chris Baker 9Chris Baker 9
I am trying to do the same thing as the original poster which is calling a REST service from one organization to another. Following this thread, I was successful except for one thing that is reallly confusing.

I was able to set up a sandbox to call itself. Let's call this one SB1. I set up the Remote Site and the Connected App and was able to have it call out to itself which is great.

But then I next tried to move all of my code to a new sandbox (Let's call it SB2). I wanted that new sandbox to try and connect to itself. So, I set up a Remote Site & Connected App, but I always get an authentication error. 

Then, I tried this next:
In SB2  I created a Remote Site with the endpoint of SB1 so that I could try calling SB1 from SB2. Also I used the client Id and client Secret of SB1.  I used my username/password for Sb2 and it worked. 

I am really confused because I didn't think I could use the url, clientId, clientSecret for SB1 with the credentials of SB2.

And further, I don't know why I can't use credentials of SB2 with the url, clientId, clientSecrent of SB2. 

I'm wondering if I only need 1 Connected App for all my Sandboxes? Any help would be apprectiated.

Thanks,
Chris 
 
s.mafoders.mafoder
Hello Pat Patterson , 

Could you please help me in this problem , 
In fact , i need to get the authorization code automatically in the controller without redirecting to the authorization URL to hget the code (using pagereference). Is there a manner to do it? 
In fact from an enduser side , when clicking on a button he is automatically connected without being interrupted with the link to get the code. 

Thank you for your help,
 
Madhuri TankMadhuri Tank
req.setHeader('Authorization', 'OAuth '+UserInfo.getSessionId());

this helped me to call a  Webservice as callout in another Webservice in same org. 
Thank you for posting solution. @ Pat Patterson
 
farukh sk hdfarukh sk hd
Hi,

This  will help you for sure.Here rest api integration is performed by taking example of two salesforce system.

https://www.sfdc-lightning.com/2019/01/salesforce-rest-api-integration.html