diff --git a/README.md b/README.md index 15e53ce..cc4625b 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,14 @@ OAuth.io PHP SDK OAuth that just works ! -This SDK allows you to use OAuth.io's server-side flow from a PHP backend, to handle access tokens from your server instead of directly from your front-end. +This SDK allows you to use OAuth.io from a PHP backend, to handle the authentication and API calls from your server instead of from your front-end, for 100+ API providers. -You can use it with one of our front-end SDKs ([JavaScript][1], [PhoneGap][2], [iOs][3], [Android][4]), which will handle the user input for the OAuth flow. - -The current version of the SDK is `0.2.0`. Older versions are deprecated. +The current version of the SDK is `0.3.2`. Older versions are deprecated. You can also get nightlies by checking out our `develop` branch. +To get more information about this SDK and its method, please refer to its [reference documentation](https://oauth.io/docs/api-reference/server/php) on OAuth.io. + Features -------- @@ -24,31 +24,19 @@ Common use-Case You don't want to use APIs directly from the front-end, but rather through web-services inside your PHP backend. -The server-side flow --------------------- - -In the server-side OAuth authentication flow, the oauth token never leaves your backend. - -To authenticate a user, the flow follows these steps : - -- Ask the backend for a unique state token. This token will be used for communicating with oauth.io -- Show a popup or redirect your user to request his permission to use his/her account on the requested provider -- The latter gives you a code, that you give to your backend -- The backend sends the code to oauth.io with other information like the oauth.io app's public key and secret. -- oauth.io responds with the access_token, that you can then store on your backend as long as it's valid -- You can then make requests to the API using that access token, directly from your backend - -As of `0.2.0` it is possible to get an automatically refreshed access token when a refresh token is available. - Installation ------------ +First of all, you'll need to set your app's backend to **PHP** in your OAuth.io [dashboard](https://oauth.io/dashboard). + +This allows you to get a refresh token from the provider if available. + You can install it through Composer by adding the following dependency to your composer.json : ```json "require": { ... - "oauth-io/oauth": "0.2.0" + "oauth-io/oauth": "0.3.0" ... }, ``` @@ -59,10 +47,10 @@ Then run in the console : $ composer install ``` -How to use it ? ---------------- +Using the SDK +------------- -The `OAuth` class is stored in the `OAuth_io` namespace. You need to include it in your file like this (make sure you have required the Composer autoloader file) : +The `OAuth` class is stored in the `OAuth_io` namespace. You need to include it in your file like this (make sure you have required the Composer autoloader file): ```php ``` -**Generating a token** +**Authenticating the user** -You need to provide your front-end with a state token, that will be used to exchange information with OAuth.io. To generate it in the back-end : +The first thing you need to do is to create an endpoint that will redirect your user to the provider's authentication page, so that the user can accept the permissions your app needs. + +In this endpoint, call the `redirect` method like this: ```php -generateStateToken(); -//?> +$oauth->redirect('the_provider', '/callback/url'); ``` -The `generateStateToken()` method returns a unique token. This token is stored in the session, and used to communicate with oauth.io. - -You have to give this token to your front-end, where you can show the user a popup for him to log in to the provider and accept your app's permissions (see further down to see how to do that). - -**Auth the user** - -To be able to make requests to a provider's API using its access token, you have to call the `auth(provider, options)` method first. This method creates a request object from either a code you got from the front-end SDK (for the first time authentication), the session (if the user was authenticated during the same session), or a credentials array that you saved earlier. +This will automatically redirect your user to the provider's website. Once he has accepted the permissions, he will be redirected to the '/callback/url' on your app, where you'll be able to retrieve a request object. -To get a request object from a code (which automatically fills up the session for further use in other endpoints), you can do like this : +In an endpoint associated to the '/callback/url', call the `auth` method with the `redirect` option set to true to get a request object, like this: ```php $request_object = $oauth->auth('the_provider', array( - 'code': $code + 'redirect' => true )); ``` `$request_object` is an object that allows you to perform requests (see further down to learn how to), and that contains the user's credentials. -You can get the credentials array if you need to save them for later use (or for a cron) like this : +**Using the session to get a request object** + +Usually, you'll want to make calls to the API several times while the user is connected to your app. Once you've authenticated the user once with a code, the session is automatically configured to work with the SDK. + +Thus, you just need to do this to get a request object: + +```php +$request_object = $oauth->auth('the_provider'); +``` + +**Saving credentials to re-generate a request object** + +You can also save the user's credentials to make requests in a cron. You can get the credentials array from a request object like this : ```php $credentials = $request_object->getCredentials(); +// Here save the $credentials array for later use ``` -The `$credentials` array contains the access token, refresh token and other information returned by the provider. - -**Retrieving a code from the front-end** - -```JavaScript -//In the front end, using the JavaScript SDK : - -OAuth.initialize('your_key'); -OAuth.popup('a_provider', { - // The state token you got from the backend - // through $oauth->generateStateToken(): - state: 'state_token' - }) -.done(function (r) { - //You need to give r.code to your backend - $.ajax({ - url: '/auth_endpoint/signin', - data: { - code: r.code - } - }) - .done(function (data, status) { - //your user is authenticated server side - //you can now call endpoints that use the OAuth.io SDK - }); -}); +Then, when you want to reuse these credentials, you can rebuild a $request_object from them: + +```php +$request_object = $oauth->auth('the_provider', array( + 'credentials' => $credentials +)); ``` **Making requests to the API** @@ -201,7 +176,10 @@ You can also call the `me(array $filters)` method from that request object. This ```php create('facebook'); +$facebook_requester = $oauth->auth('facebook', array( + 'redirect' => true +)); + $result = $facebook_requester->me(array('firstname', 'lastname', 'email')); // you'll have $result["firstname"], $result["lastname"] and $result["email"] set with the user's facebook information. @@ -210,34 +188,6 @@ $result = $facebook_requester->me(array('firstname', 'lastname', 'email')); You can refer to the OAuth.io me() feature to get more information about the fields that are returned by this method. -**Using the session** - -Usually, you'll want to make calls to the API several times while the user is connected to your app. Once you've authenticated the user once with a code, the session is automatically configured to work with the SDK. - -Thus, you just need to do this to get a request object: - -```php -$request_object = $oauth->auth('the_provider'); -``` - -**Saving credentials** - -You can also save the user's credentials to make requests in a cron. You can get the credentials array from a request object like this : - -```php -$credentials = $request_object->getCredentials(); -// Here save the $credentials array for later use -``` - - -Then, when you want to reuse these credentials, you can rebuild a $request_object from them: - -```php -$request_object = $oauth->auth('the_provider', array( - 'credentials' => $credentials -)); -``` - **Refreshing the token** If a refresh token is available and the access token is expired, the `auth` method will automatically use that refresh token to get a new access token. diff --git a/composer.json b/composer.json index 1293c15..66c861b 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "oauth-io/oauth", "description": "OAuth that just works !", "license": "Apache2", - "version": "0.2.0", + "version": "0.3.2", "authors": [ { "name": "oauth-io", @@ -12,7 +12,7 @@ "minimum-stability": "dev", "require": { "php": ">=5.3.0", - "mashape/unirest-php" : "dev-master" + "mashape/unirest-php" : "2.2.*" }, "autoload": { "psr-0": { diff --git a/src/OAuth_io/HttpWrapper.php b/src/OAuth_io/HttpWrapper.php index 4ee4f7d..ac0c37e 100644 --- a/src/OAuth_io/HttpWrapper.php +++ b/src/OAuth_io/HttpWrapper.php @@ -1,10 +1,13 @@ $value) { if (is_object($array[$key])) { @@ -15,10 +18,10 @@ private function array_map_recursive($callback, $array) { } return $array; } - + public function make_request($options) { $injector = Injector::getInstance(); - + $url = $options['url']; $method = $options['method']; $headers = $options['headers']; @@ -30,27 +33,14 @@ public function make_request($options) { } $url = str_replace('%2C', ',', $url); - \Unirest::verifyPeer($injector->ssl_verification); - if ($options['method'] == 'GET') { - $response = \Unirest::get($url, $headers); - } - - if ($options['method'] == 'POST') { - $response = \Unirest::post($url, $headers, $body); - } - - if ($options['method'] == 'PUT') { - $response = \Unirest::put($url, $headers, $body); - } - - if ($options['method'] == 'DELETE') { - $response = \Unirest::delete($url, $headers); - } - - if ($options['method'] == 'PATCH') { - $response = \Unirest::patch($url, $headers, $body); + if (isset($headers['Content-Type']) && $headers['Content-Type'] == 'application/json' && is_array($body)) { + $body = json_encode($body); } + + Request::verifyPeer($injector->ssl_verification); + $response = Request::send($options['method'], $url, $body, $headers); + return $response; } } diff --git a/src/OAuth_io/OAuth.php b/src/OAuth_io/OAuth.php index 52fcb1f..c32dad0 100644 --- a/src/OAuth_io/OAuth.php +++ b/src/OAuth_io/OAuth.php @@ -125,47 +125,71 @@ public function refreshCredentials($credentials, $force = false) { return $credentials; } + public function redirect($provider, $url) { + $urlToRedirect = 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . '://' . $_SERVER['HTTP_HOST'] . $url; + $csrf = $this->generateStateToken(); + $location = $this->injector->config['oauthd_url'] . $this->injector->config['oauthd_base'] . '/' . $provider . + '?k=' . $this->injector->config['app_key'] . '&opts=' . + urlencode(json_encode(array('state' => $csrf))) . + '&redirect_type=server&redirect_uri=' . urlencode($urlToRedirect); + header("Location: " . $location); + die(); + } + public function auth($provider, $options = array()) { - // $options can contain code, credentials, or nothing. If nothing --> session call if (!$this->initialized) { throw new NotInitializedException('You must initialize the OAuth instance.'); } - if (isset($options['code'])) { - $request = $this->injector->getRequest(); - $response = $request->make_request(array( - 'method' => 'POST', - 'url' => $this->injector->config['oauthd_url'] . $this->injector->config['oauthd_base'] . '/access_token', - 'body' => http_build_query(array( - 'code' => $options['code'], - 'key' => $this->injector->config['app_key'], - 'secret' => $this->injector->config['app_secret'] - )) , - 'headers' => array( - 'Content-Type' => 'application/x-www-form-urlencoded' - ) - )); - $credentials = json_decode(json_encode($response->body) , true); - if (isset($credentials['expires_in'])) { - $date = new \DateTime(); - $credentials['expires'] = $date->getTimestamp() + $credentials['expires_in']; + else { + + if (isset($options['redirect']) && $options['redirect']) { + $data = json_decode($_GET['oauthio'], true); + $code = $data['data']['code']; } - - if (isset($credentials['provider'])) { - $this->injector->session['oauthio']['auth'][$credentials['provider']] = $credentials; + else if (isset($options['code'])) { + $code = $options['code']; } - } else if (isset($options['credentials'])) { - $credentials = $options['credentials']; - } else { - if (isset($this->injector->session['oauthio']['auth'][$provider])) { - $credentials = $this->injector->session['oauthio']['auth'][$provider]; + + + if (isset($code)) { + $request = $this->injector->getRequest(); + $response = $request->make_request(array( + 'method' => 'POST', + 'url' => $this->injector->config['oauthd_url'] . $this->injector->config['oauthd_base'] . '/access_token', + 'body' => http_build_query(array( + 'code' => $code, + 'key' => $this->injector->config['app_key'], + 'secret' => $this->injector->config['app_secret'] + )) , + 'headers' => array( + 'Content-Type' => 'application/x-www-form-urlencoded' + ) + )); + $credentials = json_decode(json_encode($response->body) , true); + if (isset($credentials['expires_in'])) { + $date = new \DateTime(); + $credentials['expires'] = $date->getTimestamp() + $credentials['expires_in']; + } + + if (isset($credentials['provider'])) { + $this->injector->session['oauthio']['auth'][$credentials['provider']] = $credentials; + } + } else if (isset($options['credentials'])) { + $credentials = $options['credentials']; } else { - throw new NotAuthenticatedException('The user is not authenticated for that provider'); + if (isset($this->injector->session['oauthio']['auth'][$provider])) { + $credentials = $this->injector->session['oauthio']['auth'][$provider]; + } else { + throw new NotAuthenticatedException('The user is not authenticated for that provider'); + } } + $credentials = $this->refreshCredentials($credentials, isset($options['force_refresh']) ? $options['force_refresh'] : false); + $request_object = new RequestObject($credentials); + + return $request_object; } - $credentials = $this->refreshCredentials($credentials, isset($options['force_refresh']) ? $options['force_refresh'] : false); - $request_object = new RequestObject($credentials); + - return $request_object; } } diff --git a/src/OAuth_io/RequestObject.php b/src/OAuth_io/RequestObject.php index b06aaeb..4e427c4 100644 --- a/src/OAuth_io/RequestObject.php +++ b/src/OAuth_io/RequestObject.php @@ -2,10 +2,10 @@ namespace OAuth_io; class RequestObject { - + private $injector; private $credentials; - + public function __construct($credentials = array()) { $this->injector = Injector::getInstance(); $this->credentials = $credentials; @@ -18,59 +18,64 @@ public function getCredentials() { public function wasRefreshed() { return $this->credentials['refreshed'] == true; } - + private function object_to_array($obj) { return json_decode(json_encode($obj), true); } - - private function makeRequest($method, $url, $body_fields = null) { + + private function makeRequest($method, $url, $body_fields = null, $headers_field = null) { $response = null; if (!isset($this->credentials)) { throw new NotAuthenticatedException('The user is not authenticated for that provider'); } else { $prov_data = $this->credentials; $requester = $this->injector->getRequest(); - + $tokens = array(); - - $headers = array( + + $oauthio_headers = array( 'k' => $this->injector->config['app_key'] ); - + if (isset($prov_data['access_token'])) { - $headers['access_token'] = $prov_data['access_token']; + $oauthio_headers['access_token'] = $prov_data['access_token']; } if (isset($prov_data['oauth_token']) && isset($prov_data['oauth_token_secret'])) { - $headers['oauth_token'] = $prov_data['oauth_token']; - $headers['oauth_token_secret'] = $prov_data['oauth_token_secret']; - $headers['oauthv1'] = '1'; + $oauthio_headers['oauth_token'] = $prov_data['oauth_token']; + $oauthio_headers['oauth_token_secret'] = $prov_data['oauth_token_secret']; + $oauthio_headers['oauthv1'] = '1'; + } + $headers = array( + 'oauthio' => http_build_query($oauthio_headers) + ); + if (is_array($headers_field)) { + foreach ($headers_field as $key => $value) { + $headers[$key] = $value; + } } - $response = $requester->make_request(array( 'method' => $method, 'url' => $this->injector->config['oauthd_url'] . '/request/' . $this->credentials['provider'] . '/' . urlencode($url) , - 'headers' => array( - 'oauthio' => http_build_query($headers) - ) , + 'headers' => $headers, 'body' => is_array($body_fields) ? $body_fields : null )); } return $response; } - + private function makeMeRequest($filters) { if (!isset($this->credentials)) { throw new \Exception('Error'); } else { $prov_data = $this->credentials; $requester = $this->injector->getRequest(); - + $tokens = array(); - + $headers = array( 'k' => $this->injector->config['app_key'] ); - + if (isset($prov_data['access_token'])) { $headers['access_token'] = $prov_data['access_token']; } @@ -79,13 +84,13 @@ private function makeMeRequest($filters) { $headers['oauth_token_secret'] = $prov_data['oauth_token_secret']; $headers['oauthv1'] = '1'; } - + if (is_array($filters)) { $filters = array( 'filter' => join(',', $filters) ); } - + $response = $requester->make_request(array( 'method' => 'GET', 'url' => $this->injector->config['oauthd_url'] . '/auth/' . $this->credentials['provider'] . '/me', @@ -97,33 +102,33 @@ private function makeMeRequest($filters) { } return $response; } - + public function get($url) { $response = $this->makeRequest('GET', $url)->body; $response = $this->object_to_array($response); return $response; } - - public function post($url, $fields) { - $response = $this->makeRequest('POST', $url, $fields)->body; + + public function post($url, $fields, $headers = null) { + $response = $this->makeRequest('POST', $url, $fields, $headers)->body; return $this->object_to_array($response); } - - public function put($url, $fields) { - $response = $this->makeRequest('PUT', $url, $fields)->body; + + public function put($url, $fields, $headers = null) { + $response = $this->makeRequest('PUT', $url, $fields, $headers)->body; return $this->object_to_array($response); } - + public function del($url) { $response = $this->makeRequest('DELETE', $url)->body; return $this->object_to_array($response); } - - public function patch($url, $fields) { - $response = $this->makeRequest('PATCH', $url, $fields)->body; + + public function patch($url, $fields, $headers = null) { + $response = $this->makeRequest('PATCH', $url, $fields, $headers)->body; return $this->object_to_array($response); } - + public function me($filters = null) { $response = $this->makeMeRequest($filters)->body->data; return $this->object_to_array($response);