Single Sign-On

Changes to SSO as of 2020-06-07
We have changed the timestamp property from Unix-epoch seconds to Unix-epoch milliseconds. While seconds will still work we urge you migrate to use milliseconds for more a secure implementation of SSO.

The Pubble Single sign-on (SSO) solution allows you (the site owner) to easily build an internal communications platform among site admins and internal users. Once users are authenticated on your website, they do not need to sign-in again on Pubble. To enable this, you need to include existing "user details message" in our widget code (see below for details). The Pubble system will collect the user data from your side and will use it to update the users profile on Pubble, and will also provide your internal users with a seamless authenticated session on Pubble.

For SSO Pubble uses a technology called JSON Web Token (JWT) for securely exchanging authenticated user data between your system and Pubble

SSO functionality is currently available for the Live QA, Live Blog and Community QA apps

User sign-in flow

Your website
User signs into your website
Signed-in
User visits page with Pubble widget
Generate Token
Your system generates a JSON message with authenticated user data
Pass Token to Pubble
Add the JSON message to the widget embed code

Note: At this stage, we presume user has already signed in on your system, and your system is able to feed enough user info in step 1.

Add SSO Key

Before you are able to create a secure JSON message and use SSO, you first need to add an SSO key to your Pubble account. This is the key that you will use to create a secure hash of the JSON message. This key will only be known to yourself and Pubble, it is important not to share it.

To add an SSO key go to the admin center and go to Community > Settings > Single Sign-On enter an alphnumeric value, something that cannot be easily guessed.

Choose the Hash Algorithm

You can also choose the hash algorithm you wish to use to generate the signature. To select the hash algorithm used go to the admin center and go to Community > Settings > Single Sign-On

You can select between HMAC SHA1 and HMAC SHA256, the latter is considered more secure and thus is the recommended one to use

Step 1: Generate JSON-serialized user data

This needs to include the user information that you will send to Pubble for creating/updating a users profile on Pubble.

The message body must include the following case sensitive properties unless noted otherwise:

userID: Unique userID associated with that account within your database. We will use it as an identifier to add/update a users profile in the Pubble system name: Name of your user account email: Email of your user account. It must be unique avatar: Avatar URL of a user account. Set to empty if you do not have userURL: URL of user profile page within your system. Set to empty if do not have admin: Deprecated. Set to false jobTitle: Job Title / Position / tagline of user bio: A short bio of the user. Max 300 characters emailNotification: true/false set whether the user will received email notifications for replies. Default: false
{
	"userID":"uniqueId_12345678923",
	"email":"testsso@test.co",
	"admin":"false",
	"name":"pubblesso",
	"avatar":"http://png-1.findicons.com/files/icons/1072/face_avatars/300/i04.png",
	"userURL":"http://ssotest.com/",
	"jobTitle":"CEO of Widgets Co.",
	"emailNotification": "false"
}

Step 2: Timestamp (NOT Base-64 encoded)

Generate a long value timestamp when the JSON message was created. The timestamp value is current time in Unix-epoch milliseconds. If the timestamp is not current the SSO message may be rejected.

e.g. Java: long timestamp = System.currentTimeMillis();

e.g. PHP: $time = time();

Step 3: Generate Base-64 encoded user data

Generate a base64 encoded value of user JSON message which was created in step 1.

e.g. Java: String base64EncodedStr = Base64.encodeBase64(json);

Step 4: HMAC format signature

Next you need to generate a signature in the HMAC-SHA1 or HMAC-SHA256 format

The signature must be generated from the message of the following format. It is important to include the single space between the base64 encoded JSON message and the timestamp

HMAC->SHA256(pubble_sso_key, base64EncodedStr + ' ' + timestamp)

Note: base64EncodedStr was generated in step 3. The timestamp was generated in step 2.

Step 5: SSO Message

Once your signature has been generated you need to create an SSO Message which is composed of the three parts (base64EncodedStr, signature and timestamp) each separated with a single white space

Once created, it will look something like the following:

eyJ1c2VySUQiOiJ1bmlxdWVJZF8xMjM0NTY3ODkyMyIsImVtYWlsIjoidGVzdHNzb0B0ZXN0LmNvIiwiYWRtaW4iOiJmYWxzZSIsIm5hbWUiOiJwdWJibGUgc3NvIiwiYXZhdGFyIjoiaHR0cDovL3BuZy0xLmZpbmRpY29ucy5jb20vZmlsZXMvaWNvbnMvMTA3Mi9mYWNlX2F2YXRhcnMvMzAwL2kwNC5wbmciLCJ1c2VyVVJMIjoiaHR0cDovL3Nzb3Rlc3QuY29tLyJ9 3d8e70d6318af5bd1cab6580f7afdfe96b2a949d 1594057853438

Step 6: Add SSO message in widget code

Now you have the SSO message, you need to include it in the embedded Pubble widget code by adding it as the value for the following data attribute data-app-auth_info:

<div class="pubble-app PQAQ_section" data-app-id="12345" data-app-identifier="12345" data-app-auth_info="sso_message"></div>
<script type="text/javascript" src="https://cdn.pubble.io/javascript/loader.js" defer></script>

Java example code

Below is sample Java code to generate the SSO message

String PUBBLE_SSO_KEY =  "Your_SSO_key";

// Step 1. User data, replace values with authenticated user data
HashMap<String, String> message = new HashMap<String, String>();
//your userid in your system
message.put("userID","uniqueId_12345678923");
message.put("name","test sso");
// User email address
message.put("email","testsso@pubble.co");
// User avatar URL (optional)
message.put("avatar","http://png-1.findicons.com/face_avatars/300/i04.png");
// User website or profile URL (optional)
message.put("userURL","http://test.com/");
message.put("admin", "false");
message.put("jobTitle", "CEO of Widgets co.");

// Step 2. Get the timestamp
long timestamp = System.currentTimeMillis();


// Step 3. Encode user data
ObjectMapper mapper = new ObjectMapper();
String jsonMessage = mapper.writeValueAsString(message);
String base64EncodedStr = new String(Base64.encodeBase64(jsonMessage.getBytes()));

// Step 4. Generate signature
String signature = "";

try {
   	signature = calculateRFC2104HMAC(base64EncodedStr + " " + timestamp, PUBBLE_SSO_KEY);
   	// Step 5. Print out the SSO message
   	System.out.println(base64EncodedStr + " " + signature + " " + timestamp);
} catch (Exception e) {
	e.printStackTrace();
}

Node.js example code

Below is sample Javascript code to generate the SSO message

// Step 1. import crypto
var crypto = require('crypto');

// Step 2. User data, replace values with authenticated user data
var user = {
	"userID":"uniqueId_12345678923",
	"name":"test sso",
	"email":"testsso@pubble.co",
	"avatar":"http://png-1.findicons.com/face_avatars/300/i04.png",
	"userURL":"http://test.com/",
	"admin":"false",
	"jobTitle":"CEO of Widgets co."
}

// Step 3. Get the timestamp
let timestamp = new Date().getTime();

// Step 4. Encode user data
let base64EncodedStr = nodeBase64.encode(JSON.stringify(user));

// Step 5. Generate signature
var signature = crypto.createHmac('SHA256', "YOUR_SSO_SECURITY_KEY").update(base64EncodedStr + ' ' + timestamp).digest('hex');

// Step 6. Generate SSO message token
var token = base64EncodedStr + ' ' + signature + ' ' + timestamp;

PHP example code

Below is sample PHP code to generate the SSO message

/**
* JSON ENCODE
* Checks if json_encode is not available and defines json_encode
* to use php_json_encode instead
*/

function cf_json_encode($data) {
	return cfjson_encode($data);
}

function cfjson_encode_string($str) {
	if(is_bool($str)) {
		return $str ? 'true' : 'false';
	}
	return str_replace(
		array('"', '/', "\n", "\r"),
		array('\"', '\/', '\n', '\r'),
		$str);
}

function cfjson_encode($arr) {
	$json_str = '';
	if (is_array($arr)) {
		$pure_array = true;
		$array_length = count($arr);
		for ( $i = 0; $i < $array_length ; $i++) {
			if (!isset($arr[$i])) {
				$pure_array = false;
				break;
			}
		}
		if ($pure_array) {
			$json_str = '[';
			$temp = array();
			for ($i=0; $i < $array_length; $i++) {
				$temp[] = sprintf("%s", cfjson_encode($arr[$i]));
			}
			$json_str .= implode(',', $temp);
			$json_str .="]";
		}
		else {
			$json_str = '{';
			$temp = array();
			foreach ($arr as $key => $value) {
				$temp[] = sprintf("\"%s\":%s", $key, cfjson_encode($value));
			}
			$json_str .= implode(',', $temp);
			$json_str .= '}';
		}
	}
	else if (is_object($arr)) {
		$json_str = '{';
		$temp = array();
		foreach ($arr as $k => $v) {
			$temp[] = '"'.$k.'":'.cfjson_encode($v);
		}
		$json_str .= implode(',', $temp);
		$json_str .= '}';
	}
	else if (is_string($arr)) {
		$json_str = '"'. cfjson_encode_string($arr) . '"';
	}
	else if (is_numeric($arr)) {
		$json_str = $arr;
	}
	else if (is_bool($arr)) {
		$json_str = $arr ? 'true' : 'false';
	}
	else {
		$json_str = '"'. cfjson_encode_string($arr) . '"';
	}
	return $json_str;
}

}

/**
* JSON ENCODE
*/
/* pb_sso function is used to generate pubble sso information*/

function pb_sso() {
	$key = "Your_key"; // your pubble SSO key
	global $current_user;
	get_currentuserinfo(); // Get current user data from your system.
	if ($current_user->ID) {
		$avatar_tag = get_avatar($current_user->ID);
		$avatar_data = array();
		preg_match('/(src)=((\'|")[^(\'|")]*(\'|"))/i', $avatar_tag, $avatar_data);
		$avatar = str_replace(array('"', "'"), '', $avatar_data[2]);
		if (current_user_can('manage_options')) {

			$user_data = array(
				'name' => $current_user->display_name,
				'userID' => $current_user->ID,
				'avatar' => $avatar,
				'email' => $current_user->user_email,
				'userURL' => $current_user->user_url,
				'admin' => "false");
		}
		else {
			// For normal user, no need to set admin field.
			$user_data = array(
				'name' => $current_user->display_name,
				'userID' => $current_user->ID,
				'avatar' => $avatar,
				'email' => $current_user->user_email,
				'userURL' => $current_user->user_url,
			);
		}
	}
	else {
		$user_data = array();
	}


	// Get the timestamp
	$time = time();

	// Encode user data
	$user_data = base64_encode(cf_json_encode($user_data));

	// Encrypt user info with timestamp and pubble_key
	$hmac = pb_hmacsha1($user_data.' '.$time, $key);

	// Generate final SSO signature
	$payload = $user_data.' '.$hmac.' '.$time;
	return array('remote_auth_s2'=>$payload);
}

function pb_hmacsha1($data, $key) {
	$blocksize=64;
	$hashfunc='sha1';
	if (strlen($key)>$blocksize)
	$key=pack('H*', $hashfunc($key));
	$key=str_pad($key,$blocksize,chr(0x00));
	$ipad=str_repeat(chr(0x36),$blocksize);
	$opad=str_repeat(chr(0x5c),$blocksize);
	$hmac = pack(
		'H*',$hashfunc(
			($key^$opad).pack(
				'H*',$hashfunc(
					($key^$ipad).$data
					)
				)
			)
	);
	return bin2hex($hmac);
}

Deleting an SSO user

You can delete an SSO user via a special endpoint. You need to encode the data the same way as above, however you only need to include the userID in the JSON construct. For example:

{
	"userID":"uniqueId_12345678923"
}

The new SSO message then needs to be passed via the "authInfo" parameter of the following endpoint:

https://[subdomain].pubble.io/api/v2/sso/delete

Parameter Value Example
authInfo SSO Message eyJ1c2VySUQiOiJ1bmlxdWVJZF8xMjM0NTY3ODkyMzc4NiJ9 bb389516df65ac80e590ba3f8dae63e594e3369a 1594060646029

Both GET and POST requests are supported.

Note: you must replace the subdomain with your own team subdomain on Pubble.

The response will be a JSON construct containing a response code and status message:

{
	code: 200,
	status: "ok"
}

The following response codes are possible:

Status code Outcome Note
200 OK Operation successful
413 Incorrect parameter value Ensure the authInfo parameter is correct
420 Community not found Check that the subdomain is correct
421 Account not found The SSO account may have already been deleted, or the ID is incorrect
434 No key found Check that an SSO secret key is defined for the community
705 Token invalid Ensure the SSO Message data is correct or that secret key is the same
706 Timestamp invalid Ensure the timestamp is present and correct
500 Server Error Occurs if there is an issue deserializing JSON or calculating the HMAC token
521 Missing timestamp Timestamp missing from SSO Message
522 Timestamp invalid If the timestamp is not current it will be rejected. You must use a freshly generated timestamp.