Laravel SSO and pre-registration


(xaoseric ) #1

I have my own login system built with laravel that I would like to use. Is it possible for discourse to redirect to a url for login and use an external login?

For example, discourse is at discuss.example.com, and i would want users to use login.example.com. Is it possible to use the discourse api to login the user?


(Michael Downey) #2

There’s a whole sub-category here about single-sign on like this. :slight_smile: For what it’s worth, we do this and it works pretty well. The only drawback is there doesn’t yet seem to be any way to “pre-register” existing users until they manually come to Discourse and sign in.

https://meta.discourse.org/category/extensibility/sso


(Bill Ayakatubby) #3

[Recategorized since this is to do with SSO, and retitled to concisely describe the topic]


(Failcookie) #4

I am working through this process now, so I wanted to revive this to see if anyone has any tips on the best way to approach integration with Laravel.

I am currently using a plugin called, Sentinel, which is covering all of my roles/permissions on my PHP site, as well as registration and authentication. I am trying to work through the workflow… so let me know if I am way off.

I redirect the SSO URL to http://example.com/login/sso for the user to login. The user completes the information and then is redirected back to http://community.example.com (?) where the information is passed back to the site. <-- I am lost in this area. How do I let Discourse know that the user has logged in?

Second note - when the user visits the Community and is logged in on the main site, are they logged in on the Community as well?


(Khoa Nguyen) #5

This is interested. Instead of Sentiel, i’m using zizaco confide/entrust for permission/role and authenticaion. There is a php helper class i think you should take a look.


I tried to make Laravel become a sso consumer but i’m not succeed.


(Failcookie) #6

You rock!!

My only error was getting this part to work

if (!($sso->validate($payload,$sig))) {
    // invaild, deny
    header("HTTP/1.1 403 Forbidden");
    echo("Bad SSO request");
    die();
}

I removed that bit of coding from the example, sent the SSO and sig query as hidden input to the post form and then relayed the information back to my forum. I successfully registered and joined the Community… going to test some other areas now.

I am guessing that the validation is pretty important for security, so I will look at what was causing the issues exactly.


(Khoa Nguyen) #7

The class’s last commit was 5 months ago. That should be the problem.
Here is some examples of using that class with Wordpress WordPress SSO Page Template
WP Discourse SSO Plugin
P/s: actually that class was published 8 months ago.


(Failcookie) #8

That would probably be it then. I’ll be doing some testing… but it’s functional and sending data appropriately, so that’s a huge plus!

One thing that I realized for Sentinel - may not be Entrust - I have to redirect a page on the Laravel site before I redirect the session back to Discourse. It wasn’t registering the login on the Laravel part.


(Khoa Nguyen) #9

I don’t understand this part. Can you explain it?
This is validate function
’’’‘
public function validate($payload, $sig) {
$payload = urldecode($payload);
if(hash_hmac(“sha256”, $payload, $this->sso_secret) === $sig) {
return true;
} else {
return false;
}
}
’’’'
I don’t see why it doesn’t validate


(Failcookie) #10

Sure. When I pass the credentials through the variables, which are Sentinel specific.

Sentinel::authenticate($input, $remember)

I then validate the user is logged in and send the user variables over to Discourse for login. The session variable doesn’t persist with Laravel and the login does not remain on the Laravel side. I am thinking there is a better way to handle the session issue… but for now its just as easy to redirect the user to Route::get page and send that information over the Discourse.

I hope that makes sense…

I am working on testing out the validation issue now. This is pretty exciting.


(Khoa Nguyen) #11

Where do you put the sso login part?
In my code, I set sso url to my normal login form (user/login)
After user login normal, I will check that if there is sso input and then redirect to discourse instance. With this way, I don’t need edit anything in the code (only a if condition was added)


(Failcookie) #12

I created an alternative URL so I could be more flexible with the testing of the user being logged in and having to log in. I created a route “login/sso” and kept the same login form. Here is my code for reference…

Route::get('login/sso', function() {
	if(Sentinel::guest()){
		return View::make('frontend.authentication.ssologin');
	} else {
		$query = array(
			'sso' => Request::query('sso'),
			'sig' => Request::query('sig'),
		);
		return Redirect::to('login/sso/send?'. http_build_query($query));
	}

});
Route::post('login/sso', function() {

	$sso = new Discourse_SSO("SSO-KEY");

	$payload = Input::get('sso');
	$sig = Input::get('sig');


	$nonce = $sso->getNonce($payload);
	try
	{
		$input = Input::all();

		$rules = [
			'email'    => 'required|email',
			'password' => 'required',
		];

		$validator = Validator::make($input, $rules);

		if ($validator->fails())
		{
			return Redirect::back()
				->withInput()
				->withErrors($validator);
		}

		$remember = (bool) Input::get('remember', false);

		if ($user = Sentinel::authenticate(Input::all(), $remember))
		{
			return Redirect::to('login/sso/send?sso='. $payload .'&sig='. $sig);
		}

		$errors = 'Invalid login or password.';
	}
	catch (NotActivatedException $e)
	{
		$session = 'Account is not activated!';

		return Redirect::to('reactivate')->with('user', $e->getUser());
	}
	catch (ThrottlingException $e)
	{
		$delay = $e->getDelay();

		$session = "Your account is blocked for {$delay} second(s).";
		return Redirect::back()
			->withInput()
			->with('bad-login', "Your account is blocked for {$delay} second(s)");
	}

});
Route::get('login/sso/send', function() {
	$sso = new Discourse_SSO("jnoawjfweojf089q2309482jn");

	$payload = Request::query('sso');
	$sig = Request::query('sig');


	$nonce = $sso->getNonce($payload);
	$user = Sentinel::getUser();
	$user_id = $user->id; // Remember - this can be *anything*, as long as you keep it consistent!
	$email = $user->email;
	// These two are optional - feel free to delete them from the array() below
	// If you do, Discourse will generate suggestions from the provided email.
	$suggested_username = $user->username;
	$suggested_full_name = $user->first_name.' '.$user->last_name;

	// END your auth code

	$userparams = array(
		"nonce" => $nonce,
		"external_id" => $user_id,
		"email" => $email,

		// Optional - feel free to delete these two
		"username" => $suggested_username,
		"name" => $suggested_full_name
	);
	$q = $sso->buildLoginString($userparams);
	header('Location: http://community.example.com/session/sso_login?' . $q);

	exit(0);
});

(Kane York) #13

If you ever want the user to re-start the Discourse SSO workflow, redirect them to discourse.example.com/session/sso. They’ll get bounced back to your /login/sso url with a fresh nonce in the payload.

Also you really shouldn’t take out the payload signature validation.


(Khoa Nguyen) #14

That’s a known issue of this helper class. ( Figured by Wordpress sso template author)


(Failcookie) #15

I will be replacing the payload validation as soon as I can figure out the best way to process the data… for now I am just returning consistent errors validating the signature. Just being a pain at the moment…