+ Start a Discussion
joey_hoey19joey_hoey19 

PHP and OAuth 2.0 JWT Bearer Token Flow

Hi,

I am running into this error:
{
  error_description: "expired authorization code"
  error: "invalid_grant"
}
Currently trying to connect to my Sandbox 'Connected App' from a client server running PHP scripts and using OAuth 2.0 JWT Bearer Token Flow as the method of authentication. I have gone over and over this document (OAuth 2.0 JWT Bearer Token Flow (https://help.salesforce.com/apex/HTViewHelpDoc?id=remoteaccess_oauth_jwt_flow.htm&language=en_US#validate_token)) so many times but can not seem to understand why i am getting this error.

I've checked my connected App settings and provided full access already with no restrictions of IP address so I don't know why I get expired authorization code error. Maybe it is in my PHP coding from the client side requesting the 'Access Token'? Here is my Code.
 
// You need to set these three to the values for your own application
define('CONSUMER_KEY', 'abc123');
define('CONSUMER_SECRET', '1234');
define('LOGIN_BASE_URL', 'https://test.salesforce.com');

//Json Header
$h = array(
	"alg" => "RS256"	
);

$jsonH = json_encode(($h));	
$header = base64_encode($jsonH); 

//Create JSon Claim/Payload
$c = array(
	"iss" => CONSUMER_KEY, 
	"sub" => "myemail@email.com", 
	"aud" => LOGIN_BASE_URL, 
	"exp" => "1333685628"
);

$jsonC = (json_encode($c));	
$payload = base64_encode($jsonC);

//Sign the resulting string using SHA256 with RSA
$s = hash_hmac('sha256', $header.'.'.$payload, CONSUMER_SECRET);
$secret = base64_encode($s);


$token = $header . '.' . $payload . '.' . $secret;

$token_url = LOGIN_BASE_URL.'/services/oauth2/token';

$post_fields = array(
	'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
	'assertion' => $token
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $token_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

// Make the API call, and then extract the information from the response
    $token_request_body = curl_exec($ch) 
        or die("Call to get token from code failed: '$token_url' - ".print_r($post_fields, true));
Hope any one out there might be able to help me solve this!

Thanks.
Regards,
Joey
 
Best Answer chosen by joey_hoey19
Pat PattersonPat Patterson
You've hardcoded the value for the exp parameter using the value in the docs. The exp field is the "expiration time of the assertion, within five minutes, expressed as the number of seconds from 1970-01-01T0:0:0Z measured in UTC." You're giving it some time long in the past (Fri, 06 Apr 2012 04:13:48 GMT to be exact), so the server is complaining that it has expired (although the mention of auth code is a bit misleading).

In PHP, you'd generate the exp field like this:
 
$exp = strval(time() + (5 * 60));

 

All Answers

Pat PattersonPat Patterson
You've hardcoded the value for the exp parameter using the value in the docs. The exp field is the "expiration time of the assertion, within five minutes, expressed as the number of seconds from 1970-01-01T0:0:0Z measured in UTC." You're giving it some time long in the past (Fri, 06 Apr 2012 04:13:48 GMT to be exact), so the server is complaining that it has expired (although the mention of auth code is a bit misleading).

In PHP, you'd generate the exp field like this:
 
$exp = strval(time() + (5 * 60));

 
This was selected as the best answer
joey_hoey19joey_hoey19
Thanks Pat that seemed to resolve that problem however I am now running into
error_description: "expired authorization code" error: "invalid_grant"

I suspect its to do with my signatures but can't figure it out if it might be the syntax.
//Sign the resulting string using SHA256 with RSA
$s = hash_hmac('sha256', $header.'.'.$payload, CONSUMER_SECRET);
$secret = base64_encode($s);

Wondering if you can see the mistake or have any ideas on the problem?

Thanks
Joey.

 
Pat PattersonPat Patterson
I just noticed - you're using hash_hmac, but the JWT must be signed with RSA SHA256, not HMAC. To do this, you need to load your RSA private key and use openssl_sign (http://php.net/manual/en/function.openssl-sign.php). Here's some sample code based on a StackOverflow answer (http://stackoverflow.com/a/11119059):
 
// LOAD YOUR PRIVATE KEY FROM A FILE - BE CAREFUL TO PROTECT IT USING
// FILE PERMISSIONS!
$private_key = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBANDiE2+Xi/WnO+s120NiiJhNyIButVu6zxqlVzz0wy2j4kQVUC4Z
RZD80IY+4wIiX2YxKBZKGnd2TtPkcJ/ljkUCAwEAAQJAL151ZeMKHEU2c1qdRKS9
sTxCcc2pVwoAGVzRccNX16tfmCf8FjxuM3WmLdsPxYoHrwb1LFNxiNk1MXrxjH3R
6QIhAPB7edmcjH4bhMaJBztcbNE1VRCEi/bisAwiPPMq9/2nAiEA3lyc5+f6DEIJ
h1y6BWkdVULDSM+jpi1XiV/DevxuijMCIQCAEPGqHsF+4v7Jj+3HAgh9PU6otj2n
Y79nJtCYmvhoHwIgNDePaS4inApN7omp7WdXyhPZhBmulnGDYvEoGJN66d0CIHra
I2SvDkQ5CmrzkW5qPaE2oO7BSqAhRZxiYpZFb5CI
-----END RSA PRIVATE KEY-----
EOD;

// This is where openssl_sign will put the signature
$s = "";

// SHA256 in this context is actually RSA with SHA256
$algo = "SHA256";

// Sign the header and payload
openssl_sign($header.'.'.$payload, $s, $private_key, $algo);

// Base64 encode the result
$secret = base64_encode($s);
Andrzej Borkowski 3Andrzej Borkowski 3
do i need push somwhere in sf admin panel a public key ?
joey_hoey19joey_hoey19
Hi Andrzej, 
I've actually stopped this project but you need to go to Setup > Build > Create > Apps and create a new "connected App" which will produce the consumer/secret keys for you in Salesforce.

https://help.salesforce.com/htviewhelpdoc?id=connected_app_create.htm&siteLang=en_US

Thanks.
Regards,
Joey.