Building an IBM OAuth Consumer in PHP

An increasing number of IBM products make use of OAuth, for example IBM Connections and IBM SmartCloud for Social Business. For those developers new to OAuth, there is an excellent sample application on developerWorks. It’s great – if you’re writing Java. But I’ve recently worked with two business partners who are PHP shops.  They obviously required an PHP OAuth consumer, which leads me to post.  I’ll reference the IBM SmartCloud for Social Business OAuth steps, but the OAuth process is the same on IBM Connections (with one minor implementation difference). Let’s get started.

First, there are some constants that will be used throughout the code.

There are the standard OAuth parameters.

class OAuthParam {
 
	const ACCESS_TOKEN = 'access_token';
	const REFRESH_TOKEN = 'refresh_token';
	const EXPIRES_IN = 'expires_in';
 
	const CLIENT_ID = 'client_id';
	const CLIENT_SECRET = 'client_secret';
	const CALLBACK_URI = 'callback_uri';
 
	const AUTHORIZATION_CODE = 'authorization_code';
	const CODE = "code";
	const GRANT_TYPE = 'grant_type';
	const RESPONSE_TYPE = 'response_type';
 
	const OAUTH_ERROR = 'oauth_error';
 
}

Next, there are the respective server endpoints to obtain tokens.

class IBMOAuthEndpoints {
 
	// IBM SmartCloud for Social Business
	const SC4SB_AUTH_PATH = '/manage/oauth2/authorize';
	const SC4SB_TOKEN_PATH = '/manage/oauth2/token';
 
	// IBM Connections
	const CNX_AUTH_PATH = '/oauth2/endpoint/connectionsProvider/authorize';
	const CNX_TOKEN_PATH = '/oauth2/endpoint/connectionsProvider/token';
 
}

And finally, some constants are specific to IBM SmartCloud for Social Business.

class SmartCloud {
 
	const E1_SERVER = 'https://apps.na.collabserv.com';
	const C1_SERVER = 'https://apps.na.collabservtest.lotus.com';
 
	const ACCESS_TOKEN_EXPIRATION = 7200;	// 2 hours
	const REFRESH_TOKEN_EXPIRATION = 7776000;	// 90 days
 
}

I have created two sample scripts. One for SmartCloud.

require('../oauth/IBMOAuthV2.php');
require('../oauth/IBMOAuthEndpoints.php');
require('../oauth/SmartCloud.php');
 
// vendor application (must be SSL for callback to work)
// this is the current URL hosting this PHP script
$callbackUrl = 'https://localhost/SC4SB/SC4SBOAuth2_0.php';
 
// Step 1: Register the application
$clientId =  'app_20072407_1349820195761';
$clientSecret = '1250ec8a59c5f9a8f68dc77d341ab32bd2d71d81ccf98d77174765d58233a1ebf980b496d7bff7b366973363cde82ed3b1e21174461c39f8a97d5c6a4a09a2b65efb29dcd8c64a4f3acde395ef453c5fd441365dfe8ee90aa9416774796ffe3d189662219fd384fcb86a119637964a0a31d5fd5084e47e8b50'; // not really a secret
 
// Step 2: Obtain authorization code
// Step 3: Exchange authorization code for access and refresh tokens
$sc4sbOAuth = new IBMOAuthV2(SmartCloud::C1_SERVER, $clientId, $clientSecret, $callbackUrl, 
		IBMOAuthEndpoints::SC4SB_AUTH_PATH, IBMOAuthEndpoints::SC4SB_TOKEN_PATH);
$accesToken = $sc4sbOAuth->getAccessToken();
 
// Step 4: Use the access token to allow API access
$ch = curl_init(SmartCloud::C1_SERVER . "/api/bss/resource/customer");
curl_setopt_array($ch, $sc4sbOAuth->options);
curl_setopt($ch, CURLOPT_HTTPHEADER, array($sc4sbOAuth->getAuthorizationHeader()));
 
print curl_exec($ch);
 
curl_close($ch);

And a similar sample for Connections.

require('../oauth/IBMOAuthV2.php');
require('../oauth/IBMOAuthEndpoints.php');
 
const CNX_SERVER = 'http://connections.demos.ibm.com:9081';
 
// vendor application this is the current URL hosting this PHP script
$callbackUrl = 'https://localhost/Connections/ConnectionsOAuth2_0.php';
 
// Step 1: Register the application
$clientId =  'php-sample';
$clientSecret = 'NXl0GROIu9p6YFKU4i1LI5qZ9OnrBL14y8QQg68brJ7GPEdgn0ed9tI'; // not really a secret
 
// Step 2: Obtain authorization code
// Step 3: Exchange authorization code for access and refresh tokens
$sc4sbOAuth = new IBMOAuthV2(CNX_SERVER, $clientId, $clientSecret, $callbackUrl,
		IBMOAuthEndpoints::CNX_AUTH_PATH, IBMOAuthEndpoints::CNX_TOKEN_PATH);
 
// Step 4: Use the access token to allow API access
$ch = curl_init(CNX_SERVER . '/connections/opensocial/oauth/rest/activitystreams/@me/@all');
curl_setopt_array($ch, $sc4sbOAuth->options);
curl_setopt($ch, CURLOPT_HTTPHEADER, array($sc4sbOAuth->getAuthorizationHeader()));
 
print curl_exec($ch);
 
curl_close($ch);

Let’s look at each of the steps individually.

Step 1: Register the application

Registration is straight forward. See IBM SmartCloud for Social Business and IBM Connections. Here is an example of my commands for IBM Connections.

execfile(‘oauthAdmin.py’)
OAuthApplicationRegistrationService.addApplication(‘php-sample’, ‘PHP Sample’,’https://localhost/Connections/ConnectionsOAuth2_0.php’)
clientSecret = OAuthApplicationRegistrationService.getApplicationById(‘SBTK’).get(‘client_secret’)
print clientSecret

Step 2: Obtain authorization code

We’ll begin with the request for an authorization token. The IBMOAuthV2 class first checks if your browser has an access token or a refresh token from a previous session. These are stored as cookies, and if not, the following code executes. When the code completes, the user will be prompted to login to IBM SmartCloud for Social Business or Connections.  By logging in, the user consents to the third party’s use of his or her data.

private function getAuthorizationCode(){
	$code = $this->getUrlParam($_SERVER['REQUEST_URI'], OAuthParam::CODE);
 
	if($code == NULL){
		// Step 2: Obtain authorization code
		syslog(LOG_INFO, 'Obtaining authorization code for client ID ' .  $this->clientId);
 
		$url = $this->sc4sbUrl . $this->authPath . '?' .
				OAuthParam::RESPONSE_TYPE . '=' . OAuthParam::CODE .
				'&' . OAuthParam::CALLBACK_URI . '=' . $this->callbackUrl .
				'&' . OAuthParam::CLIENT_ID . '=' . $this->clientId;
 
		syslog(LOG_INFO, $url);
 
		header('Location: ' . $url);
		exit;
 
		// the result is SC4SB or Connections returning to the callbackUrl
	} else {
		return $code;
	}
}

After login, the user is redirected back to the third party’s application.  The authorization code is provided as part of the redirect, which is then used to obtain the access token.

Step 3: Exchange authorization code for access and refresh tokens

private function exchangeTokens(){
	// Step 3: Exchange authorization code for access and refresh tokens
	$code = $this->getAuthorizationCode();
 
	syslog(LOG_INFO, 'Authorizing client ID ' .  $this->clientId . ' using authorization code ' . $code);
 
	$endpoint = $this->sc4sbUrl . $this->tokenPath;
 
	syslog(LOG_INFO, 'OAuth consumer created for ' . $endpoint);
 
	$ch = curl_init($endpoint);
 
	$fields = OAuthParam::CALLBACK_URI . '=' .  urlencode($this->callbackUrl) .
	'&' . OAuthParam::CLIENT_SECRET . '=' .  urlencode($this->clientSecret) .
	'&' . OAuthParam::CLIENT_ID . '=' .  urlencode($this->clientId) .
	'&' . OAuthParam::GRANT_TYPE . '=' . urlencode(OAuthParam::AUTHORIZATION_CODE) .
	'&' . OAuthParam::CODE . '=' . urlencode($code);
 
	syslog(LOG_INFO, 'Adding POST fields ' . $fields);
 
	curl_setopt_array($ch, $this->options);
	curl_setopt($ch, CURLOPT_POSTFIELDS,  $fields);
	curl_setopt($ch, CURLOPT_POST, 5);
 
	// if the result is false, check curl_error($ch);
	$result = curl_exec($ch);
 
	if(!$result){
		syslog(LOG_ERR, 'Failed Step 3');
		syslog(LOG_ERR, 'Authorization server returned ' . curl_getinfo($ch, CURLINFO_HTTP_CODE));
 
		// TODO Goto error page
		exit;
	} else {
		syslog(LOG_INFO, 'Authorization server returned ' . curl_getinfo($ch, CURLINFO_HTTP_CODE));
	}
 
	// If the request is successful, the following parameters are returned
	// in the body of the response with an HTTP response code of 200:
	// refresh_token, access_token, issued_on, expires_in, token_type
 
	// the response body differs between Connections and SC4SB
	$result = $this->normalizeBodyData($result);
 
	parse_str($result, $this->tokenData);
 
	curl_close($ch);
 
	// you can store the refresh token to request a new access token in the future
	// e.g. for up to 90 days
	syslog(LOG_INFO, 'Successfully obtained the following parameters from SmartCloud for Social Business');
	syslog(LOG_INFO, $body);
	syslog(LOG_INFO, print_r($this->tokenData, TRUE));
 
	$this->cacheTokens($this->tokenData[OAuthParam::ACCESS_TOKEN],
			$this->tokenData[OAuthParam::REFRESH_TOKEN]);
}

The above code makes a direct request from the third party’s application server to either IBM SmartCloud for Social Business or Connections.  It’s important to be aware “who” makes the request – the application server, not the user’s browser.  Any firewalls or port restrictions between the third party application and IBM products can lead to failure.

You may have noticed that I have a helper function normalizeBodyData.

private function normalizeBodyData($body){
	syslog(LOG_INFO, 'Normalizing response body ' . $body);
	// Connections body
	// {"access_token":"czv49WRypFyFsFpAJOqlzwQ7jyMsW7SXKMcGXknP","token_type":"bearer","expires_in":43199,"scope":"","refresh_token":"k05LL9G1QzlwGeSQhcWXHi2Drq04wgD0ZCbw2vw9T4VtowSXnZ"}
 
	// normalize to the SC4SB format
	$cnxTokens = array("\"", ":", ",", "{", "}");
	$sc4sbTokens = array("", "=", "&", "", "");
 
	$normalized = str_replace($cnxTokens, $sc4sbTokens, $body);
 
	syslog(LOG_INFO, 'Normalized ' . $normalized);
 
	return $normalized;
}

The response format from IBM SmartCloud for Social Business and IBM Connections differs. I’ve chosen to normalize the Connections response to the SmartCloud format for use in the next step.  Now with the access token available, the third party application can make an API call.

Step 4: Use the access token to allow API access

For example, access the activity stream in IBM Connections.  While the below code retrieves data, creation of an activity stream entry by the third party is possible.  And the entry would appear as though it was made by the user who approved the access – not a third party.

// Step 4: Use the access token to allow API access
$ch = curl_init(CNX_SERVER . '/connections/opensocial/oauth/rest/activitystreams/@me/@all');
curl_setopt_array($ch, $sc4sbOAuth->options);
curl_setopt($ch, CURLOPT_HTTPHEADER, array($sc4sbOAuth->getAuthorizationHeader()));

Another example is accessing the Business Support System in SmartCloud.

// Step 4: Use the access token to allow API access
$ch = curl_init(SmartCloud::C1_SERVER . "/api/bss/resource/customer");
curl_setopt_array($ch, $sc4sbOAuth->options);
curl_setopt($ch, CURLOPT_HTTPHEADER, array($sc4sbOAuth->getAuthorizationHeader()));

Using the data returned from the above code is where /* your code goes here */.

One final step is obtaining a new access token. The access token used to make API calls will expire in two hours for SmartCloud. The code I’ve written stores the access token as a cookie that has an expiration based on the response from the server.  The code also stores the refresh token, which – for SmartCloud – is valid for up to 90 days.  It may be used to re-obtain access tokens.  After the refresh token expires, you’ll need to perform the entire OAuth dance again. The code I’ve written is functional, but edge cases related to exactly when the refresh token expires needs to be handled better.  One final note is that while SmartCloud for Social Business lists several ways to make the refresh request, I’ve found only the parameterized URL approach works.

Step 5: Get a new access token after the access token has expired

private function refreshAccessToken(){
	syslog(LOG_INFO, 'Refreshing access_token using refresh token ' . $this->getRefreshToken());
 
	$refreshParams = OAuthParam::CLIENT_SECRET .'=' .  $this->clientSecret .
	'&' . OAuthParam::CLIENT_ID . '=' .  $this->clientId .
	'&' . OAuthParam::GRANT_TYPE . '=' . OAuthParam::REFRESH_TOKEN .
	'&' . OAuthParam::REFRESH_TOKEN . '=' .  $this->getRefreshToken();
 
	// set SC4SB refresh query to the URL
	if($this->tokenPath == IBMOAuthEndpoints::SC4SB_TOKEN_PATH){
		syslog(LOG_INFO, 'Adding SC4SB refresh query');
 
		$ch = curl_init($this->sc4sbUrl . $this->tokenPath . '?' . $refreshParams);
 
		curl_setopt_array($ch, $this->options);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $auth);
	} else {
		// set Connections POST parameters
		syslog(LOG_INFO, 'Adding Connections refresh POST fields ' . $fields);
 
		$ch = curl_init($this->sc4sbUrl . $this->tokenPath);
 
		curl_setopt_array($ch, $this->options);
		curl_setopt($ch, CURLOPT_POSTFIELDS,  $refreshParams);
		curl_setopt($ch, CURLOPT_POST, 5);
	}
 
	// if the result if false, check curl_error($ch);
	$result = curl_exec($ch);
 
	// the response body differs between Connections and SC4SB
	$result = $this->normalizeBodyData($result);
 
	// If the request is successful, the following parameters are returned
	// in the body of the response with an HTTP response code of 200:
	// refresh_token, access_token, issued_on, expires_in, token_type
 
	if(!$result){
		syslog(LOG_ERR, 'Failed Step 5');
		syslog(LOG_ERR, 'Authorization server returned ' . $result);
	}
 
	parse_str($result, $this->tokenData);
 
	curl_close($ch);
 
	syslog(LOG_INFO, 'Obtained new access_token ' . $this->tokenData[OAuthParam::ACCESS_TOKEN]);
 
	$this->cacheTokens($this->tokenData[OAuthParam::ACCESS_TOKEN],
			$this->tokenData[OAuthParam::REFRESH_TOKEN], $this->tokenData[OAuthParam::EXPIRES_IN]);
}

Download the code and happy coding.

3 thoughts on “Building an IBM OAuth Consumer in PHP”

Leave a Reply

Your email address will not be published.