diff options
author | Arnold Daniels <arnold@jasny.net> | 2015-09-25 16:09:30 +0200 |
---|---|---|
committer | Arnold Daniels <arnold@jasny.net> | 2015-09-27 16:54:20 +0200 |
commit | 4f16e6cfdd26f1bdf9aff620643ec61a1db4c7fc (patch) | |
tree | 515d28670edd8fce63e93e585698260702ec7488 | |
parent | 2f5760bec45ad3b42cb9a4c0468c85a70c19867e (diff) | |
download | sso-4f16e6cfdd26f1bdf9aff620643ec61a1db4c7fc.zip sso-4f16e6cfdd26f1bdf9aff620643ec61a1db4c7fc.tar.gz sso-4f16e6cfdd26f1bdf9aff620643ec61a1db4c7fc.tar.bz2 |
Fixed AJAX support
Now using JSONP rather than an image
Updated README
Removed on() methods on broker (feature isn't supported yet)
-rw-r--r-- | README.md | 26 | ||||
-rw-r--r-- | examples/ajax-broker/api.php | 21 | ||||
-rw-r--r-- | examples/ajax-broker/app.js | 20 | ||||
-rw-r--r-- | examples/ajax-broker/index.html | 2 | ||||
-rw-r--r-- | examples/server/MySSOServer.php | 6 | ||||
-rw-r--r-- | src/Broker.php | 60 | ||||
-rw-r--r-- | src/Server.php | 15 | ||||
-rw-r--r-- | tests/_bootstrap.php | 3 |
8 files changed, 72 insertions, 81 deletions
@@ -1,7 +1,8 @@ -Simple Single Sign-On for PHP (Ajax compatible) +Single Sign-On for PHP (Ajax compatible) --- -Associated websites often share user information, so a visitor only has to register once and can use that username and password for all sites. A good example for this is Google. You can use you google account for GMail, Blogger, iGoogle, google code, etc. This is nice, but it would be even nicer if logging in for GMail would mean I’m also logged in for the other websites. For that you need to implement [single sign-on](http://en.wikipedia.org/wiki/Single_sign-on) (SSO). +Jasny\SSO is a relatively simply and strait forward solution for an single sign on (SSO) implementation. With SSO, +logging into a single website will authenticate you for all affiliate sites. There are many single sign-on applications and protocols. Most of these are fairly complex. Applications often come with full user management solutions. This makes them difficult to integrate. Most solutions also don’t work well with AJAX, because redirection is used to let the visitor log in at the SSO server. @@ -56,24 +57,29 @@ The client logs in, sending the username and password to the broker. The broker [](http://blog.jasny.net/wp-content/uploads/sso-diagram_binck.png) -You visit another broker. It also checks for a token cookie. Since each broker is on their own domain, they have different cookies, so no token cookie will be found. The broker will redirect to the server attach to the user session. +#### Broker -The server attaches a session key generated for this broker, which differs from the one for the first broker. It attaches it to the user session. This is the same session the first broker used. The server will redirect back to the original URL. +When creating a Jasny\SSO\Broker instance, you need to pass the server url, broker id and broker secret. The broker id +and secret needs to be registered at the server (so fetched when using `getBrokerInfo($brokerId)`). -The client requests the index page at the broker. The broker will request user information from the server. Since the visitor is already logged in, the server returns this information. The index page is shown to the visitor. +Next you need to call `attach()`. This will generate a token an redirect the client to the server to attach the token +to the client's session. If the client is already attached, the function will simply return. -## Using AJAX / Javascript +When the session is attached you can do actions as login/logout or get the user's info. SSO and AJAX / RIA applications often don’t go well together. With this type of application, you do not want to leave the page. The application is static and you get the data and do all actions through AJAX. Redirecting an AJAX call to a different website won’t because of cross-site scripting protection within the browser. -With this solution the client only needs to attach the session by providing the server with a token generated by the broker. That attach request doesn’t return any information. After attaching the client doesn’t talk at all to the server any more. Authentication can be done as normal. +## Examples [](http://blog.jasny.net/wp-content/uploads/sso-diagram_ajax.png) The client check for the token cookie. If it doesn’t exists, he requests the attach URL from the broker. This attach url includes the broker name and the token, but not a original request URL. The client will open the received url in an <img> and wait until the image is loaded. -The server attaches the browser session key to the user session. When it’s done it outputs a PNG image. When this image is received by the client, it knows the server has connected the sessions and the broker can be used for authentication. The broker will work as a proxy, passing commands and requests to the sso server and return results to the client. + php -S localhost:9000 -t examples/server/ + export SSO_SERVER=http://localhost:9000 SSO_BROKER_ID=Alice SSO_BROKER_SECRET=8iwzik1bwd; php -S localhost:9001 -t examples/broker/ + export SSO_SERVER=http://localhost:9000 SSO_BROKER_ID=Greg SSO_BROKER_SECRET=7pypoox2pc; php -S localhost:9002 -t examples/broker/ + export SSO_SERVER=http://localhost:9000 SSO_BROKER_ID=Julias SSO_BROKER_SECRET=ceda63kmhp; php -S localhost:9003 -t examples/ajax-broker/ -## To conclude +Now open some tabs and visit http://localhost:9001, http://localhost:9002 and http://localhost:9003. -By connecting sessions, you give the broker the power to act as the client. This method can only be used if you trust all brokers involved. The login information is send through the broker, which he can easily store if the broker has bad intentions. +_Note that after logging in, you need to refresh on the other brokers to see the effect._ diff --git a/examples/ajax-broker/api.php b/examples/ajax-broker/api.php index 30d9607..996b813 100644 --- a/examples/ajax-broker/api.php +++ b/examples/ajax-broker/api.php @@ -14,14 +14,25 @@ if (empty($_REQUEST['command']) || !method_exists($broker, $_REQUEST['command']) try { $result = $broker->{$_REQUEST['command']}(); } catch (Exception $e) { - http_response_code($e->getCode() ?: 500); + $status = $e->getCode() ?: 500; $result = ['error' => $e->getMessage()]; } +// JSONP +if (!empty($_GET['callback'])) { + if (!isset($result)) $result = null; + if (!isset($status)) $status = isset($result) ? 200 : 204; + + header('Content-Type: application/javascript'); + echo $_GET['callback'] . '(' . json_encode($result) . ', ' . $status . ')'; + return; +} + +// REST if (!$result) { http_response_code(204); - exit(); +} else { + http_response_code(isset($status) ? $status : 200); + header("Content-Type: application/json"); + echo json_encode($result); } - -header("Content-Type: application/json"); -echo json_encode($result); diff --git a/examples/ajax-broker/app.js b/examples/ajax-broker/app.js index 0de88f0..7a627c9 100644 --- a/examples/ajax-broker/app.js +++ b/examples/ajax-broker/app.js @@ -9,12 +9,13 @@ function attach() { var req = $.ajax({ url: 'api.php?command=attach', + crossDomain: true, dataType: 'jsonp' }); - req.done(function(data, code, error) { + req.done(function(data, code) { if (code && code >= 400) { // jsonp failure - showError(error); + showError(data.error); return; } @@ -22,7 +23,7 @@ }); req.fail(function(jqxhr) { - showError(jqxhr.responseJSON || jqxhr.textResponse) + showError(jqxhr.responseJSON || jqxhr.textResponse) }); } @@ -36,6 +37,7 @@ function doApiRequest(command, params, callback) { var req = $.ajax({ url: 'api.php?command=' + command, + method: params ? 'POST' : 'GET', data: params, dataType: 'json' }); @@ -70,7 +72,7 @@ * @param info */ function showUserInfo(info) { - $('body').removeClass('anonymous, authenticated'); + $('body').removeClass('anonymous authenticated'); $('#user-info').html(''); if (info) { @@ -89,17 +91,17 @@ $('#login-form').on('submit', function(e) { e.preventDefault(); - $('#error').text('').show(); + $('#error').text('').hide(); var data = { - username: $(this).find('input[name="username"]').value, - password: $(this).find('input[name="password"]').value + username: this.username.value, + password: this.password.value }; - + doApiRequest('login', data, showUserInfo); }); $('#logout').on('click', function() { - doApiRequest('logout', null, function() { showUserInfo(null); }); + doApiRequest('logout', {}, function() { showUserInfo(null); }); }) }(jQuery); diff --git a/examples/ajax-broker/index.html b/examples/ajax-broker/index.html index 62497bf..8b8a98b 100644 --- a/examples/ajax-broker/index.html +++ b/examples/ajax-broker/index.html @@ -18,7 +18,7 @@ <div class="container"> <h1>Single Sign-On Ajax demo</h1> - <div id="error" class="alert alert-error" style="display: none;"></div> + <div id="error" class="alert alert-danger" style="display: none;"></div> <form id="login-form" class="form-horizontal state anonymous"> <div class="form-group"> diff --git a/examples/server/MySSOServer.php b/examples/server/MySSOServer.php index b97eeb0..ca523b0 100644 --- a/examples/server/MySSOServer.php +++ b/examples/server/MySSOServer.php @@ -3,6 +3,11 @@ use Jasny\ValidationResult; use Jasny\SSO; +/** + * Example SSO server. + * + * Normally you'd fetch the broker info and user info from a database, rather then declaring them in the code. + */ class MySSOServer extends SSO\Server { /** @@ -83,4 +88,3 @@ class MySSOServer extends SSO\Server return $user; } } - diff --git a/src/Broker.php b/src/Broker.php index 1306ab4..a9ffdd7 100644 --- a/src/Broker.php +++ b/src/Broker.php @@ -1,7 +1,7 @@ <?php namespace Jasny\SSO; -require_once __DIR__ . '/../vendor/autoload.php'; +use Jasny\ValidationResult; /** * Single sign-on broker. @@ -40,7 +40,6 @@ class Broker * @var array */ protected $userinfo; - /** @@ -125,7 +124,7 @@ class Broker 'broker' => $this->broker, 'token' => $this->token, 'checksum' => hash('sha256', 'attach' . $this->token . $_SERVER['REMOTE_ADDR'] . $this->secret) - ]; + ] + $_GET; return $this->url . "?" . http_build_query($data + $params); } @@ -173,7 +172,7 @@ class Broker * @param string $method HTTP method: 'GET', 'POST', 'DELETE' * @param string $command Command * @param array|string $data Query or post parameters - * @return array + * @return array|object */ protected function request($method, $command, $data = null) { @@ -183,7 +182,7 @@ class Broker curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); - if ($method === 'POST') { + if ($method === 'POST' && !empty($data)) { $post = is_string($data) ? $data : http_build_query($data); curl_setopt($ch, CURLOPT_POSTFIELDS, $post); } @@ -215,19 +214,20 @@ class Broker * Only brokers marked trused can collect and send the user's credentials. Other brokers should omit $username and * $password. * - * @param string $username - * @param string $password - * @return object + * @param string $username + * @param string $password + * @return array user info + * @throws Exception if login fails eg due to incorrect credentials */ public function login($username = null, $password = null) { - if (!isset($username)) $username = $_POST['username']; - if (!isset($password)) $password = $_POST['password']; + if (!isset($username) && isset($_POST['username'])) $username = $_POST['username']; + if (!isset($password) && isset($_POST['password'])) $password = $_POST['password']; $result = $this->request('POST', 'login', compact('username', 'password')); $this->userinfo = $result; - return $result; + return $this->userinfo; } /** @@ -235,7 +235,7 @@ class Broker */ public function logout() { - return $this->request('logout'); + $this->request('POST', 'logout'); } /** @@ -249,40 +249,4 @@ class Broker return $this->userinfo; } - - - /** - * Handle notifications send by the SSO server - * - * @param string $event - * @param object $data - */ - public function on($event, $data) - { - if (method_exists($this, "on{$event}")) $this->{"on{$event}"}($data); - } - - /** - * Handle a login notification - */ - protected function onLogin($data) - { - $this->userinfo = $data; - } - - /** - * Handle a logout notification - */ - protected function onLogout() - { - $this->userinfo = null; - } - - /** - * Handle a notification about a change in the userinfo - */ - protected function onUserinfo($data) - { - $this->userinfo = $data; - } } diff --git a/src/Server.php b/src/Server.php index 02d779d..bb0222a 100644 --- a/src/Server.php +++ b/src/Server.php @@ -153,15 +153,17 @@ abstract class Server */ protected function detectReturnType() { - if (!empty($_REQUEST['return_url'])) { + if (!empty($_GET['return_url'])) { $this->returnType = 'redirect'; + } elseif (!empty($_GET['callback'])) { + $this->returnType = 'jsonp'; } elseif (strpos($_SERVER['HTTP_ACCEPT'], 'image/') !== false) { $this->returnType = 'image'; } elseif (strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) { $this->returnType = 'json'; - } elseif (!empty($_REQUEST['return_url'])) { - $this->returnType = 'jsonp'; } + + error_log($this->returnType); } /** @@ -204,7 +206,8 @@ abstract class Server } if ($this->returnType === 'jsonp') { - echo $_REQUEST['callback'] . "(200);"; + $data = json_encode(['success' => 'attached']); + echo $_REQUEST['callback'] . "($data, 200);"; } if ($this->returnType === 'redirect') { @@ -233,7 +236,7 @@ abstract class Server { $this->startSession(); - if (empty($_POST['username'])) $this->fail("No user specified", 400); + if (empty($_POST['username'])) $this->fail("No username specified", 400); if (empty($_POST['password'])) $this->fail("No password specified", 400); $validation = $this->authenticate($_POST['username'], $_POST['password']); @@ -287,7 +290,7 @@ abstract class Server if ($http_status === 500) trigger_error($message, E_USER_WARNING); if ($this->returnType === 'jsonp') { - echo $_REQUEST['callback'] . "($http_status, '" . addslashes($message) . "');"; + echo $_REQUEST['callback'] . "(" . json_encode(['error' => $message]) . ", $http_status);"; exit(); } diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php index 243f9c8..6c8c4f5 100644 --- a/tests/_bootstrap.php +++ b/tests/_bootstrap.php @@ -1,2 +1,3 @@ <?php -// This is global bootstrap for autoloading + +require_once __DIR__ . '/../vendor/autoload.php'; |