function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
dxjonesdxjones 

REST API seems to require Interactive Login - this fails for web app

I have been trying out the new SF REST API, and "Getting Started" examples work fine, at least, ... they seemed to work, as long as I happened to be already logged in Salesforce.

 

I have been following this PHP code example (which does the same thing as the original Java example):

 

http://developer.force.com/cookbook/recipe/interact-with-the-forcecom-rest-api-from-php

 

The problem arises when trying to do REST API calls from a web application.

 

(example:  web app periodically synchronizes MySQL database on PHP server with Salesforce Records.)

 

This fails because the REST API seems to require the user to be logged in.

If the user is not logged in, the OAuth2 redirects to an interactive login page.

 

This, of course, fails when there is no live person around at the time the web app is trying to do the REST API calls.

 

Is there any way to perform REST API calls without requiring an Interactive Login??

 

For instance, ... can the authentication tokens returned using OAuth2 be stored and re-used indefinitely??

 

Example code would be much appreciated.

 

-- David Jones

 

 

Best Answer chosen by Admin (Salesforce Developers) 
dxjonesdxjones

I just want to wrap up this thread, ... finally.

 

In the end, I was able to authenticate with username+password using the REST API by whitelisting the IP address of my server (as was suggested earlier in this thread).

 

Thanks to Pat for helping to navigate through this.

 

cheers,

  David Jones

 

All Answers

Pat PattersonPat Patterson

Hi David,

 

Here's code to obtain an access token from username/password:

 

 

define("CLIENT_ID", "PUT_YOUR_CONSUMER_KEY_HERE");
define("CLIENT_SECRET", "PUT_YOUR_CONSUMER_SECRET_HERE");
define("LOGIN_URI", "https://login.salesforce.com");

define("USERNAME", "user@example.com");
define("PASSWORD", "password");

function salesforce_login($username, $password) {
    $params = "grant_type=password&client_id=".CLIENT_ID.
            "&client_secret=".CLIENT_SECRET.
            "&username=$username&password=$password";
    
    $token_url = LOGIN_URI . "/services/oauth2/token";
    
    $curl = curl_init($token_url);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
    $json_response = curl_exec($curl);
    $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    if ( $status != 200 ) {
        die("Error: call to URL $token_url failed with status $status");
    }
    
    return json_decode($json_response, true);
}

$response = salesforce_login(USERNAME, PASSWORD);
$access_token = $response["access_token"];
$instance_url = $response["instance_url"];

As far as I know, the resulting token is governed by the settings at Setup/Security Controls/Session Settings, so you will periodically need to get a new access token.

 

dxjonesdxjones

Hi Pat,

 

Thanks so much for your code example.  It works like a charm!

 

Being able to avoid the interactive login is essential for a web app using the REST API.

 

I see under "Setup> Security Controls> Session Settings" that I can extend my session to 8 hours.

 

What is the best way for a developer to re-authenticate at the end of a session?

 

1. Is there a REST API endpoint I can use verify my current token is still valid?

 

2. Alternatively, I could check the age of the current token, and if more than 8 hours, I could login again.

 

3. Finally, I could catch if an error related to an expired token occurred, and re-authenticate then.

 

Suggestions?

 

thanks in advance,

  David

 

 

Pat PattersonPat Patterson

Hi David,

 

I haven't been working with this stuff for long, so I don't necessarily know the best way, but I've used your #3 - catch the error on using an expired token, and re-authenticate (see more below).

 

If you want to do #2, take a look at the response from my salesforce_login() function, you'll see $response["issued_at"], which is the number of milliseconds since the Unix Epoch (January 1 1970 00:00:00 GMT). You could save this (or, indeed, save the entire $response) and use it to check the age of the token on each call.

 

On #1 - I don't think there is a REST API endpoint to specfically verify the validity of a token. In any case, since the majority of calls will be made with a valid token, the most efficient approach is to just assume the token is valid and handle the error when it expires.

 

Here's my code to do exactly that:

 

 

// Wraps the actual GET - does the OAuth, and relogs in if necessary
function do_get($request) {
    // This is the response object that we retrieved from the session elsewhere
    global $response;

    // Do a maximum of two GETs
    for ($i = 0; $i < 2; $i++) {
        $access_token = $response["access_token"];
        $instance_url = $response["instance_url"];

        $curl = curl_init($instance_url.$request);
        curl_setopt($curl, CURLOPT_HEADER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HTTPHEADER,
                array("Authorization: OAuth $access_token"));

        $json_response = curl_exec($curl);
        $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        curl_close($curl);

        if ($status == 200) {
            return json_decode($json_response, true);
        } else if ($i == 0) {
            // Refresh the token for one more try...
            $response = salesforce_login(USERNAME, PASSWORD);

            $_SESSION["sflogin"] = $response;
        }
    }

    var_dump($json_response);
    die("Error: call to URL $url failed with status $status\n");
}

 

Cheers,


Pat

dxjonesdxjones

Hi Pat,

 

I thought I had your example working yesterday, ... but today I get an HTTP Status Code 500 (Internal Error) when I try to log in with username + password.

 

Since "Internal Error" sounds like it is a problem on the SF side, and not on my side, ... I am stuck.

 

Any suggestions on what I can try to resolve the issue?

 

thanks in advance,

  David Jones

 

Pat PattersonPat Patterson

Hi David - I'll see what I can dig up. I don't think you should be getting a 500!

chuckmortimorechuckmortimore

Hi David 

 

Is there more information about the error you can post to the list?   The actual detail of the 500 would help diagnose.

 

thanks

 

 

dxjonesdxjones

Here is the error output I get:  (personal details replaced with "X" below, but correct values used in code)

 

 

token_url = https://login.salesforce.com/services/oauth2/token
params = grant_type=password&client_id=X&client_secret=X&username=X&password=X
Error: call to URL https://login.salesforce.com/services/oauth2/token failed with status 500

 

 

Here is the PHP code (I followed Pat Patterson's example):

 

 

<?
require_once 'config.php';

function salesforce_login($username, $password) {
    $params = 'grant_type=password' .
    	'&client_id=' . CLIENT_ID .
		'&client_secret=' . CLIENT_SECRET .
		'&username=' . urlencode($username) .
		'&password=' . urlencode($password);
    
    $token_url = LOGIN_URI . "/services/oauth2/token";
    
    $curl = curl_init($token_url);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
    $json_response = curl_exec($curl);
    $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    if ( $status != 200 ) {
    	$cr = "\n";
    	echo 'token_url = ' . $token_url . '<br/>' . $cr;
    	echo 'params = ' . $params . '<br/>' . $cr;
        die("Error: call to URL $token_url failed with status $status");
    }
    
    return json_decode($json_response, true);
}

$response = salesforce_login(SF_USERNAME, SF_PASSWORD);
$access_token = $response["access_token"];
$instance_url = $response["instance_url"];

// - - - - -
ob_start();
$cr = "\n";
echo 'Salesforce Login Response:' . $cr . $cr;
print_r($$var);
$txt = ob_get_contents(); ob_end_clean();
$filename = 'debug/sf' . time() . '.txt';
file_put_contents($filename, $txt);
// - - - - -

// - - - - -
// not used
// $auth_url = LOGIN_URI
//        . "/services/oauth2/authorize?response_type=code&client_id="
//        . CLIENT_ID . "&redirect_uri=" . urlencode(REDIRECT_URI);

header('Location: ' . REDIRECT_URI);
?>

 

 

Pat PattersonPat Patterson

Hi David,

 

Could you modify your code to print $json_response as well?

 

Cheers,

 

Pat

dxjonesdxjones

Hi Pat,

 

Here is what was in the $json_response:

 

 

An internal server error has occurred
An error has occurred while processing your request. The salesforce.com support team has been notified of the problem. If you believe you have additional information that may be of help in reproducing or correcting the error, please contact Salesforce Support. Please indicate the URL of the page you were requesting, any error id shown on this page as well as any other related information. We apologize for the inconvenience. 

Thank you again for your patience and assistance. And thanks for using salesforce.com! 

Error ID: 393337126-8764 (-893201179)

 

 

Does this help you track down the issue??

 

thanks,

  David

 

chuckmortimorechuckmortimore

Hi David....

 

We looked up your error, and I believe we've fixed the 500 in the patch that will go out this evening.   You should receive a proper error message in protocol after that, rather than that generic server error

 

 

chuckmortimorechuckmortimore

Hi David - one more question here after thinking about it some more....

 

We may fix the 500 being thrown from the server, but that may just give you a less brutal error message....it might not solve your underlying issue.   Can you describe the client application you're building in more detail so I can speculate on root cause?

dxjonesdxjones

Hi Chuck,

 

The good news, in a sense, is that it hits this error immediately during the login attempt, so it is highly reproducible.

 

I am following Pat Patterson's PHP code (which appears earlier in this thread)

which does an OAuth2 login with "grant_type=password", and the username + password are supplied.

 

The client application is a "web app" that will connect to synchronize an external MySQL DB with the Salesforce records.

 

Some of the Getting Started examples seem to be geared towards a mobile smart phone application where a human is present,

so the OAuth2 process can kick to a login page (if needed) before continuing.  In my client application, there is no human present,

so I need to supply the username+password, plus the other SF credientials associated with my application.

 

Since you said the "fix" should get pushed this evening, I will check on Thursday to see if my oauth login works and I'll post my results.

 

-- David

 

chuckmortimorechuckmortimore

The bad news:  all you'll see on Thursday is likely a general error message...along the lines of  "invalid credentials"  

 

The good news:   The root cause should be fixable, and is what we designed that flow for.

 

There are likely 1 or 2 things going wrong here

 

 

  1. The client_id you're using belongs to a different org than the user you are using.    Is this just for a single org, or are you building something that would be packaged and available to multiple salesforce orgs?
  2. The client is being identity challenged ( and this flow doesn't handle this well yet )    The easiest way to deal with this would be to add a IP restriction to the integration user you're using for the IP address of the server where the client runs
Let me know if you have any questions or that needs clarification.

 

dxjonesdxjones

I am still getting the same 500 error.  Not sure if the patch has been installed yet.  The only thing that changes is the Error ID.  It is different each time I try.

Presumably this might enable a Salesforce engineer to dive in and see debugging details that might be in a log entry connected with this unique Error ID.

 

Error ID: 769001916-17189 (-893201179)
chuckmortimorechuckmortimore

Actually, you just need to wait until tomorrow.   We rolled back the patch last night as we weren't entirely happy with it's quality.    

 

Please do look at my earlier post though - you'll likely just get a better error message.  You'll still need to correct the underlying issue.

Pat PattersonPat Patterson

Hi David,

 

I just tried your code with the minimum number of tweaks to make it just run from the command line for me:

 

 

<?php
define("CLIENT_ID", "xxxxxxxx");
define("CLIENT_SECRET", "xxxxxxxx");
define("LOGIN_URI", "https://login.salesforce.com");

define("SF_USERNAME", "xxxxxxxx");
define("SF_PASSWORD", "xxxxxxxx");

function salesforce_login($username, $password) {
    $params = 'grant_type=password' .
        '&client_id=' . CLIENT_ID .
                '&client_secret=' . CLIENT_SECRET .
                '&username=' . urlencode($username) .
                '&password=' . urlencode($password);

    $token_url = LOGIN_URI . "/services/oauth2/token";

    $curl = curl_init($token_url);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
    $json_response = curl_exec($curl);
    $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    if ( $status != 200 ) {
        $cr = "\n";
        echo 'token_url = ' . $token_url . '<br/>' . $cr;
        echo 'params = ' . $params . '<br/>' . $cr;
        echo 'response = ' . $json_response . '<br/>' . $cr;
        die("Error: call to URL $token_url failed with status $status$cr");
    }

    return json_decode($json_response, true);
}

$response = salesforce_login(SF_USERNAME, SF_PASSWORD);

print_r($response);
?>

 It works fine:

 

 

 

ppatterson-ltm:~ ppatterson$ php restauth.php 
Array
(
    [id] => https://login.salesforce.com/id/00D50000000IZ3Z/00550000001fg5O
    [issued_at] => 1291329911118
    [instance_url] => https://na3.salesforce.com
    [signature] => Ys3Ah5vnkZlSc+lXhbWyHZ7LjgDESlUQX18zirgZNok=
    [access_token] => xxxxxxxx
)

 

If this exact same code (with your client id, secret, username, password) still produces a 500 error, I guess it must be something in your org config. It's very odd that you had it working, then it stopped working. Is everything else the same - are you using the same client id/secret/username/password as when it was working?

 

dxjonesdxjones

Hi Pat,

 

I have 2 updates.

 

1.  I gather the SF_PASSWORD actually needs the SF_SECURITY_TOKEN appended.

 (I discovered this when trying out SoqlXplorer.)

Although this may be obvious to a Salesforce guru, it is not at all obvious to a newbie.

 

2.  I tried appending my SF_SECURITY_TOKEN, but it still fails.

With the recent patch, I now get Status = 400, "Invalid Credentials".

 

At this point, maybe I should just give you my actual credentials (all the "xxxxxx" in your code samepl)

and let you try it on your end.  Perhaps will get us to the source of the error more quickly.

 

Send me a note:  dxjones@gmail.com

and I'll reply with the necessary info.

 

thanks in advance,

  David

 

chuckmortimorechuckmortimore

HI David 

 

Per my earlier message, it appears your client is being identity challenged ( and this flow doesn't handle this well yet ) .   The easiest way to deal with this would be to whitelist the IP address of the server where this is running, or add an IP restriction to the integration user you're using for the IP address of the server where the client runs.     Support for using the API token with that flow is coming soon.

dxjonesdxjones

I think I fooled myself when I said earlier I had it working.

 

In fact, I had 2 versions.  One kicked me to the interactive login, and the other tried to use username/password for the human-not-present situation.  I must have accidentally tried the 1st version (thinking it was the 2nd), and since I was already logged in, Salesforce did not kick me to the login page.

 

So I think the username/password login has always been failing for me.

 

I am sure it is something simple if Pat Patterson has it working on his end.  The only challenge tracking down the obstacle.

 

I would be happy to "hand over the keys" to you or Pat.  You might be able to figure it out immediately.

 

I *am* able to use "SoqlXplorer" which asks for Username and Password (although not stated anywhere, the Password is *actually* (Password + SecurityToken).  That login works.  Pat's PHP code works (for him).  Yet, I am stuck.

 

Shoot me an email at dxjones@gmail.com and I'll hand you he keys and you (or Pat) can try it yourself.

chuckmortimorechuckmortimore

If you're attempting the login from the same box as SoqlXplorer, and it is requiring the security token, than that is likely your issue.

 

The Oauth Username/Password flow does not yet support using the API token.  We are adding this in Spring 11.    In the meantime, you need to either 1) whitelist your IP address, or 2) add an IP address restriction to your profile, and login from an allowed IP address.    Once Spring 11 ships, the token will be an option as well. 

Pat PattersonPat Patterson

[Copying from our private email thread for the record...]

 

An alternative to IP whitelisting is to use SOAP authentication as described by Reid:

 

 

require_once ('soapclient/SforcePartnerClient.php');

define("SF_USERNAME", 'user@example.com');
define("SF_PASSWORD", 'xxxxxxxx');
define("SF_SECURITY_TOKEN", 'abcdef1234567890');

$mySforceConnection = new SforcePartnerClient();
$mySforceConnection->createConnection("soapclient/partner.wsdl.xml");
$mySforceConnection->login(SF_USERNAME, SF_PASSWORD.SF_SECURITY_TOKEN);
$access_token = $mySforceConnection->getSessionId();
$instance_url = "https://".parse_url($mySforceConnection->getLocation(), PHP_URL_HOST);

echo "$access_token\n$instance_url\n";

You can now use $access_token and $instance_url as if you'd obtained them via OAuth.

 

dxjonesdxjones

I just want to wrap up this thread, ... finally.

 

In the end, I was able to authenticate with username+password using the REST API by whitelisting the IP address of my server (as was suggested earlier in this thread).

 

Thanks to Pat for helping to navigate through this.

 

cheers,

  David Jones

 

This was selected as the best answer