Discourse a SSO provider for Wordpress

AI-generated summary

A user is seeking to implement a solution where Discourse serves as the SSO (Single Sign-On) provider for their WordPress site. They already have over 1,000 users on Discourse and want to allow these users to access their WordPress site without needing to create separate accounts.

@codinghorror mentioned that Discourse’s SSO provider is built-in, but not well-documented. The user has configured Discourse as the SSO provider but is unsure about redirecting WordPress logins to Discourse.

@erlend_sh shared a link to the documentation on using Discourse as an SSO provider, which includes a PHP script by @paxmanchris that implements Discourse as an SSO provider.

@simon mentioned that their WordPress plugin, WP-Discourse, has an option to allow users to log in to WordPress through Discourse. The plugin allows Discourse to function similarly to other social login options.

@hjalali asked several questions about the plugin, including:

  • Creating a custom login page
  • Using Discourse avatars instead of Gravatar
  • Passing metadata from Discourse to WordPress
  • Redirecting users to their original site after login
  • Logging in to both Discourse and WordPress simultaneously without needing to re-login.

Hi all,

Im looking at implementing a solution in wordpress where discourse becomes the SSO provider.
I already have 1000+ users on discourse and want to allow users to have accounts on the wordpress site.

Does anyone know whether this is possible today using exiting plugins?

thanks

SSO provider is built in, search site settings. Not sure it’s very well documented at the moment but @sam added it a while ago.

thanks Jeff,

I’ve configured discourse as the SSO provider, but unsure whether a plugin exists for Wordpress to redirect to logins to discourse.

How does one re-direct the Wordpress logins to discourse.

I’ve installed WP-OAUTH in the hope that one can copy an existing provider.php file and modify to get it to work with Discourse. Copied the gitbhub one, settings are below if you any one trying something similar. Not working yet.

<?php

// start the user session for maintaining individual user states during the multi-stage authentication flow:
session_start();

# DEFINE THE OAUTH PROVIDER AND SETTINGS TO USE #
$_SESSION['WPOA']['PROVIDER'] = 'Github';
define('HTTP_UTIL', get_option('wpoa_http_util'));
define('CLIENT_ENABLED', get_option('wpoa_github_api_enabled'));
define('CLIENT_ID', get_option('wpoa_github_api_id'));
define('CLIENT_SECRET', get_option('wpoa_github_api_secret'));
define('REDIRECT_URI', rtrim(site_url(), '/') . '/');
define('SCOPE', 'user'); // PROVIDER SPECIFIC: "user" is the minimum scope required to get the user's id from Github
define('URL_AUTH', "http://meta.bommachine.co.uk/session/sso_provider?");
define('URL_TOKEN', "http://meta.bommachine.co.uk/session/access_token?");
define('URL_USER', "http://meta.bommachine.co.uk/session/user?");
# END OF DEFINE THE OAUTH PROVIDER AND SETTINGS TO USE #

// remember the user's last url so we can redirect them back to there after the login ends:
if (!$_SESSION['WPOA']['LAST_URL']) {$_SESSION['WPOA']['LAST_URL'] = strtok($_SERVER['HTTP_REFERER'], "?");}

# AUTHENTICATION FLOW #
// the oauth 2.0 authentication flow will start in this script and make several calls to the third-party authentication provider which in turn will make callbacks to this script that we continue to handle until the login completes with a success or failure:
if (!CLIENT_ENABLED) {
	$this->wpoa_end_login("This third-party authentication provider has not been enabled. Please notify the admin or try again later.");
}
elseif (!CLIENT_ID || !CLIENT_SECRET) {
	// do not proceed if id or secret is null:
	$this->wpoa_end_login("This third-party authentication provider has not been configured with an API key/secret. Please notify the admin or try again later.");
}
elseif (isset($_GET['error_description'])) {
	// do not proceed if an error was detected:
	$this->wpoa_end_login($_GET['error_description']);
}
elseif (isset($_GET['error_message'])) {
	// do not proceed if an error was detected:
	$this->wpoa_end_login($_GET['error_message']);
}
elseif (isset($_GET['code'])) {
	// post-auth phase, verify the state:
	if ($_SESSION['WPOA']['STATE'] == $_GET['state']) {
		// get an access token from the third party provider:
		get_oauth_token($this);
		// get the user's third-party identity and attempt to login/register a matching wordpress user account:
		$oauth_identity = get_oauth_identity($this);
		$this->wpoa_login_user($oauth_identity);
	}
	else {
		// possible CSRF attack, end the login with a generic message to the user and a detailed message to the admin/logs in case of abuse:
		// TODO: report detailed message to admin/logs here...
		$this->wpoa_end_login("Sorry, we couldn't log you in. Please notify the admin or try again later.");
	}
}
else {
	// pre-auth, start the auth process:
	if ((empty($_SESSION['WPOA']['EXPIRES_AT'])) || (time() > $_SESSION['WPOA']['EXPIRES_AT'])) {
		// expired token; clear the state:
		$this->wpoa_clear_login_state();
	}
	get_oauth_code($this);
}
// we shouldn't be here, but just in case...
$this->wpoa_end_login("Sorry, we couldn't log you in. The authentication flow terminated in an unexpected way. Please notify the admin or try again later.");
# END OF AUTHENTICATION FLOW #

# AUTHENTICATION FLOW HELPER FUNCTIONS #
function get_oauth_code($wpoa) {
	$params = array(
		'response_type' => 'code',
		'client_id' => CLIENT_ID,
		'scope' => SCOPE,
		'state' => uniqid('', true),
		'redirect_uri' => REDIRECT_URI,
	);
	$_SESSION['WPOA']['STATE'] = $params['state'];
	$url = URL_AUTH . http_build_query($params);
	header("Location: $url");
	exit;
}

function get_oauth_token($wpoa) {
	$params = array(
		'grant_type' => 'authorization_code',
		'client_id' => CLIENT_ID,
		'client_secret' => CLIENT_SECRET,
		'code' => $_GET['code'],
		'redirect_uri' => REDIRECT_URI,
	);
	$url_params = http_build_query($params);
	switch (strtolower(HTTP_UTIL)) {
		case 'curl':
			$url = URL_TOKEN . $url_params;
			$curl = curl_init();
			curl_setopt($curl, CURLOPT_URL, $url);
			curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
			curl_setopt($curl, CURLOPT_POST, 1);
			curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
			// PROVIDER NORMALIZATION: Reddit requires sending a User-Agent header...
			// PROVIDER NORMALIZATION: Reddit requires sending the client id/secret via http basic authentication
			curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, (get_option('wpoa_http_util_verify_ssl') == 1 ? 1 : 0));
			curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, (get_option('wpoa_http_util_verify_ssl') == 1 ? 2 : 0));
			$result = curl_exec($curl);
			break;
		case 'stream-context':
			$url = rtrim(URL_TOKEN, "?");
			$opts = array('http' =>
				array(
					'method'  => 'POST',
					'header'  => 'Content-type: application/x-www-form-urlencoded',
					'content' => $url_params,
				)
			);
			$context = $context  = stream_context_create($opts);
			$result = @file_get_contents($url, false, $context);
			if ($result === false) {
				$wpoa->wpoa_end_login("Sorry, we couldn't log you in. Could not retrieve access token via stream context. Please notify the admin or try again later.");
			}
			break;
	}
	// parse the result:
	parse_str($result, $result_obj); // PROVIDER SPECIFIC: Github encodes the access token result as a querystring by default
	$access_token = $result_obj['access_token']; // PROVIDER SPECIFIC: this is how Github returns the access token KEEP THIS PROTECTED!
	//$expires_in = $result_obj['expires_in']; // PROVIDER SPECIFIC: Github does not return an access token expiration!
	//$expires_at = time() + $expires_in; // PROVIDER SPECIFIC: Github does not return an access token expiration!
	// handle the result:
	if (!$access_token) {
		// malformed access token result detected:
		$wpoa->wpoa_end_login("Sorry, we couldn't log you in. Malformed access token result detected. Please notify the admin or try again later.");
	}
	else {
		$_SESSION['WPOA']['ACCESS_TOKEN'] = $access_token;
		//$_SESSION['WPOA']['EXPIRES_IN'] = $expires_in; // PROVIDER SPECIFIC: Github does not return an access token expiration!
		//$_SESSION['WPOA']['EXPIRES_AT'] = $expires_at; // PROVIDER SPECIFIC: Github does not return an access token expiration!
		return true;
	}
}

function get_oauth_identity($wpoa) {
	// here we exchange the access token for the user info...
	// set the access token param:
	$params = array(
		'access_token' => $_SESSION['WPOA']['ACCESS_TOKEN'], // PROVIDER SPECIFIC: the access token is passed to Github using this key name
	);
	$url_params = http_build_query($params);
	// perform the http request:
	switch (strtolower(HTTP_UTIL)) {
		case 'curl':
			$url = URL_USER . $url_params; // TODO: we probably want to send this using a curl_setopt...
			$curl = curl_init();
			curl_setopt($curl, CURLOPT_URL, $url);
			curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // PROVIDER SPECIFIC: Github requires the useragent for all api requests
			// PROVIDER NORMALIZATION: Reddit requires that we send the access token via a bearer header...
			//curl_setopt($curl, CURLOPT_HTTPHEADER, array('x-li-format: json')); // PROVIDER SPECIFIC: I think this is only for LinkedIn...
			curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
			$result = curl_exec($curl);
			$result_obj = json_decode($result, true);
			break;
		case 'stream-context':
			$url = rtrim(URL_USER, "?");
			$opts = array('http' =>
				array(
					'method' => 'GET',
					'user_agent' => $_SERVER['HTTP_USER_AGENT'], // PROVIDER NOTE: Github requires the useragent for all api requests
					'header'  => "Authorization: token " . $_SESSION['WPOA']['ACCESS_TOKEN'],
				)
			);
			$context = $context  = stream_context_create($opts);
			$result = @file_get_contents($url, false, $context);
			if ($result === false) {
				$wpoa->wpoa_end_login("Sorry, we couldn't log you in. Could not retrieve user identity via stream context. Please notify the admin or try again later.");
			}
			$result_obj = json_decode($result, true);
			break;
	}
	// parse and return the user's oauth identity:
	$oauth_identity = array();
	$oauth_identity['provider'] = $_SESSION['WPOA']['PROVIDER'];
	$oauth_identity['id'] = $result_obj['id']; // PROVIDER SPECIFIC: this is how Github returns the user's unique id
	//$oauth_identity['email'] = $result_obj['email']; //PROVIDER SPECIFIC: this is how Github returns the email address
	if (!$oauth_identity['id']) {
		$wpoa->wpoa_end_login("Sorry, we couldn't log you in. User identity was not found. Please notify the admin or try again later.");
	}
	return $oauth_identity;
}
# END OF AUTHENTICATION FLOW HELPER FUNCTIONS #
?>

Is this working ? I can’t find any post about integrating discourse as a sso provider for wordpress

Looking for something similar. Can’t find much via search. I’m thinking this should probably be an official option in wp-discourse (using Discourse as the authority rather than WordPress).

Any update on this one. WP struggles to have a proper user profile. Discourse seems to be the obvious choice when using with WP.

Using Discourse as an SSO provider is documented here:

That howto also links to a PHP script by @paxmanchris that implements Discourse as SSO provider:

거의 4년 만에 업데이트가 있는지 확인하려고 이 글을 다시 올립니다.

우리는 Discourse를 중심으로 WordPress 사이트를 구축하고 있습니다. 사용자가 Discourse에 계속 머무를 수 있도록(그것이 제공하는 모든 이점을 활용하면서) 가입을 강제하고, WordPress에서는 이를 SSO(단일 로그인) 기회로 활용하고자 합니다.

이와 유사한 경험을 하신 분이 계신가요? 혹시 어떤 어려움이 있었는지 공유해 주실 수 있을까요?

저희 WordPress 플러그인에는 사용자가 Discourse를 통해 WordPress에 로그인할 수 있도록 하는 옵션이 있습니다. 이를 통해 Discourse는 Facebook을 통한 WordPress 로그인과 같은 다른 소셜 로그인 옵션과 유사한 방식으로 작동합니다. 이 기능을 설정하는 옵션은 플러그인의 SSO Client 옵션 탭에서 찾을 수 있습니다.

정말 멋진 플러그인을 만들어 주셔서 감사합니다.
몇 가지 질문이 있습니다:

  1. 일반적인 wp-admin 로그인 페이지 대신 다른 로그인 페이지를 만들 수 있을까요?
  2. Gravatar나 다른 서비스 대신 Discourse의 아바타를 가져올 수 있을까요?
  3. 메타데이터를 전송할 수 있을까요? (예: 사용자가 등록할 때 Riot ID처럼 username#2123과 같은 값을 입력하는 커스텀 폼을 사용 중인데, 이 정보를 WordPress에도 전달할 수 있을까요?)
  4. SSO를 설정했는데, 제 Discourse가 메인 도메인이고 WordPress가 서브도메인인 상황에서 모든 것이 잘 작동합니다. 하지만 누군가가 Discourse에 로그인하더라도 WordPress 사이트의 홈 페이지로 리다이렉트됩니다(이것은 원치 않는 동작입니다). Discourse에서 로그인한 사용자는 Discourse에 머물고, WordPress에서 로그인한 사용자는 WordPress에서 로그인하도록 할 수 있을까요?
  5. Discourse에 로그인한 상태에서 WordPress 사이트에서도 "로그인된 상태"가 되도록 하여 WordPress에서 다시 로그인할 필요가 없게 할 수 있을까요?

이렇게 많은 질문을 드려 죄송합니다 …