diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 335 |
1 files changed, 243 insertions, 92 deletions
@@ -3,12 +3,10 @@ oauth2-server-php [](http://travis-ci.org/bshaffer/oauth2-server-php) -A library for implementing an OAuth2 Server in php +An OAuth2.0 Server in PHP! [View the Full Working Demo](http://brentertainment.com/oauth2) ([code](https://github.com/bshaffer/oauth2-server-demo)) -[View the Full Working Demo!](http://brentertainment.com/oauth2) ([code](https://github.com/bshaffer/oauth2-server-demo)) - -Autoloading ------------ +Installation +------------ This library follows the zend [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) standards. A number of autoloaders exist which can autoload this library for that reason, but if you are not using one, you can register the `OAuth2_Autoloader`: @@ -18,12 +16,12 @@ require_once('/path/to/oauth2-server-php/src/OAuth2/Autoloader.php'); OAuth2_Autoloader::register(); ``` -If you use a package library like [Composer](http://getcomposer.php), add the following to `composer.json` +Using [Composer](http://getcomposer.php)? Add the following to `composer.json`: ``` { "require": { - "bshaffer/oauth2-server-php": "v0.7", + "bshaffer/oauth2-server-php": "dev-develop", ... }, ... @@ -32,127 +30,217 @@ If you use a package library like [Composer](http://getcomposer.php), add the fo And then run `composer.phar install` -> Checkout out the tag `v0.7` will ensure your application doesn't break from backwards-compatibility issues, but also this means you -> will not receive the latest changes. To ride the bleeding edge of development, use `dev-develop` instead. +> It is highly recommended you check out the [`v0.8`](https://github.com/bshaffer/oauth2-server-php/tree/v0.8) tag to +> ensure your application doesn't break from backwards-compatibility issues, but also this means you +> will not receive the latest changes. + +Learning OAuth2.0 +----------------- + +If you are new to OAuth2, take a little time first to look at the [Oauth2 Demo Application](http://brentertainment.com/oauth2) and the [source code](https://github.com/bshaffer/oauth2-server-demo), and read up on [OAuth2 Flows](http://drupal.org/node/1958718). For everything else, consult the [OAuth2.0 Specification](http://tools.ietf.org/html/rfc6749) Get Started ----------- -Before getting started, take a look at the [Oauth2 Demo Application](http://brentertainment.com/oauth2) and the [source code](https://github.com/bshaffer/oauth2-server-demo) for a concrete example of this library in action. - -The quickest way to get started is to use the following code, plugging in your database information -to the constructor of `OAuth2_Storage_Pdo`: +Here is an example of a bare-bones OAuth2 Server implementation: ```php $storage = new OAuth2_Storage_Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password)); $server = new OAuth2_Server($storage); -$server->addGrantType(new OAuth2_GrantType_UserCredentials($storage)); // or some other grant type. This is the simplest -$server->handleTokenRequest(OAuth2_Request::createFromGlobals())->send(); +$server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage)); // or any grant type you like! +$server->handleTokenRequest(OAuth2_Request::createFromGlobals(), new OAuth2_Response())->send(); ``` -Let's break this down line by line. The first line is how the OAuth2 data is stored. -There are several built in storage types, for your convenience. To use PDO Storage, -instantiate the `OAuth2_Storage_Pdo` class and provide the database connection arguments: +This library requires you to define a `Storage` object, containing instrutions on how to interact with objects in your storage +layer such as [OAuth Clients](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/ClientInterface.php) and +[Authorization Codes](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/AuthorizationCodeInterface.php). +Built-in storage classes include [PDO](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/Pdo.php), +[Redis](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/Redis.php), and +[Mongo](https://github.com/bshaffer/oauth2-server-php/blob/develop/src/OAuth2/Storage/Mongo.php). The interfaces allow (and encourage) +the use of your own Storage objects to fit your application's implementation. + +Once you've created a storage object, pass it to the server object and define which Grant Types your server is to support. See +the list of supported [Grant Types](#grant-types) below. + +The final step, once the Server object is set up, is to handle the incoming request. Consult the [Server Methods](#server-methods), or +follow the [Step-by-Step Walkthrough](#step-by-step-walkthrough) to familiarize yourself with the types of requests involved in +OAuth2.0 workflows. + +Step-by-Step Walkthrough +------------------------ + +The following instructions provide a detailed walkthrough to help you get an OAuth2 server +up and running. To see the codebase of an existing OAuth2 server implementing this library, +check out the [OAuth2 Demo](https://github.com/bshaffer/oauth2-server-demo). + +### Define your Schema + +The quickest way to get started is to use the following schema to create the default database: + +```sql +CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT); +CREATE TABLE oauth_access_tokens (access_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT); +CREATE TABLE oauth_authorization_codes (authorization_code TEXT, client_id TEXT, user_id TEXT, redirect_uri TEXT, expires TIMESTAMP, scope TEXT); +CREATE TABLE oauth_users (username TEXT, password TEXT, first_name TEXT, last_name TEXT); +CREATE TABLE oauth_refresh_tokens (refresh_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT); +``` + +### Create a Token Controller + +The first thing you will do is create the **Token Controller**. This is the URI which returns an OAuth2.0 Token to the client. +Here is an example of a token controller in the file `token.php`: ```php +// error reporting (this is a demo, after all!) +ini_set('display_errors',1);error_reporting(E_ALL); + +// Autoloading (composer is preferred, but for this example let's just do this) +require_once('path/to/oauth2-server-php/src/OAuth2/Autoloader.php'); +OAuth2_Autoloader::register(); + +// $dsn is the Data Source Name for your database, for exmaple "mysql:dbname=my_oauth2_db;host=localhost" $storage = new OAuth2_Storage_Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password)); + +// Pass a storage object or array of storage objects to the OAuth2 server class $server = new OAuth2_Server($storage); -``` -> Note: `$dsn` is the Data Source Name for your database. For example, if you are using MySQL your dsn will -> look something like `mysql:dbname=my_database;host=localhost`. If you are using sqlite, your dsn will look -> something like `sqlite://path/to/my/file.sqlite`. +// Add the "Client Credentials" grant type (it is the simplest of the grant types) +$server->addGrantType(new OAuth2_GrantType_ClientCredentials($storage)); -The next step is to add a grant type. This example uses the "User Credentials" grant type, which grants a token based on -explicit user credentials passed to the request. The [OAuth2 spec](http://tools.ietf.org/html/rfc6749) has no -restrictions on the number of grant types your server can support and therefore you can add more than one grant type to -your $server ... read about this [below](https://github.com/bshaffer/oauth2-server-php#grant-types). Each grant type -also requires storage, so pass the existing storage to the constructor: +// Handle a request for an OAuth2.0 Access Token and send the response to the client +$server->handleTokenRequest(OAuth2_Request::createFromGlobals(), new OAuth2_Response())->send(); +``` -```php -$server->addGrantType(new OAuth2_GrantType_UserCredentials($storage)); +Congratulatons! You have created a **Token Controller**! Do you want to see it in action? Run the following SQL to +create an OAuth Client: + +```sql +INSERT INTO oauth_clients (client_id, client_secret, redirect_uri) VALUES ("testclient", "testpass", "http://fake/"); ``` -Call the `grantAccessToken` method to validate the request for the user credentials grant type. This will return the token -if successful. Access the server's response object to send the successful response back, or the error response if applicable: +Now run the following from the command line: -```php -$server->handleTokenRequest(OAuth2_Request::createFromGlobals())->send(); +```bash +curl -u testclient:testpass http://localhost/token.php -d 'grant_type=client_credentials' ``` -This creates the `OAuth2_Request` object from PHP global variables (most common, you can override this if need be) and sends it to the server -for assessment. The response by default is in json format, and includes the access token if successful, and error codes if not. +> Note: http://localhost/token.php assumes you have the file `token.php` on your local machine, and you have +> set up the "localhost" webhost to point to it. This may vary for your application. -> If you are just getting started, use the SQL below to create the database tables for the default MySQL setup -> ```sql -> CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT); -> CREATE TABLE oauth_access_tokens (access_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT); -> CREATE TABLE oauth_authorization_codes (authorization_code TEXT, client_id TEXT, user_id TEXT, redirect_uri TEXT, expires TIMESTAMP, scope TEXT); -> CREATE TABLE oauth_refresh_tokens (refresh_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT); -> ``` +If everything works, you should receive a response like this: -Server Methods --------------- +```json +{"access_token":"03807cb390319329bdf6c777d4dfae9c0d3b3c35","expires_in":3600,"token_type":"bearer","scope":null} +``` -> ...an end-user (resource owner) can grant a printing -> service (client) access to her protected photos stored at a photo -> sharing service (resource server), without sharing her username and -> password with the printing service. Instead, she authenticates -> directly with a server trusted by the photo sharing service -> (authorization server), which issues the printing service delegation- -> specific credentials (access token). -> -> ~ OAuth2 ([draft #31](http://tools.ietf.org/html/rfc6749#section-1)) +### Create a Resource Controller -Most OAuth2 APIs will have endpoints for `Authorize Requests`, `Token Requests`, and `Resource Requests`. The `OAuth2_Server` object has methods to handle each of these requests. +Now that you are creating tokens, you'll want to validate them in your APIs. Here is an +example of a resource controller in the file `resource.php`: -### Authorize Requests +```php +// error reporting again +ini_set('display_errors',1);error_reporting(E_ALL); -An endpoint requiring the user to authenticate, which redirects back to the client with an `authorization code`. +// Autoloading again +require_once('path/to/oauth2-server-php/src/OAuth2/Autoloader.php'); +OAuth2_Autoloader::register(); -**methods**: +// create your storage again +$storage = new OAuth2_Storage_Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password)); -`handleAuthorizeRequest` - * Receives a request object for an authorize request, returns a response object with the appropriate response +// create your server again +$server = new OAuth2_Server($storage); -`validateAuthorizeRequest` - * Receives a request object, returns false if the incoming request is not a valid Authorize Request. If the request -is valid, returns an array of retrieved client details together with input. -Applications should call this before displaying a login or authorization form to the user +// Handle a request for an OAuth2.0 Access Token and send the response to the client +if (!$server->verifyResourceRequest(OAuth2_Request::createFromGlobals(), new OAuth2_Response())) { + $server->getResponse()->send(); + die; +} +echo json_encode(array('success' => true, 'message' => 'You accessed my APIs!')); +``` -### Token Requests +Now run the following from the command line: -An endpoint which the client uses to exchange the `authorization code` for an `access token`. +```bash +curl http://localhost/resource.php -d 'access_token=YOUR_TOKEN' +``` -**methods**: +> Note: Use the value returned in "access_token" from the previous step in place of YOUR_TOKEN -`grantAccessToken` +If all goes well, you should receive a response like this: - * Receives a request object for a token request, returns a token if the request is valid. +```json +{"success":true,"message":"You accessed my APIs!"} +``` -`handleTokenRequest` +### Create an Authorize Controller - * Receives a request object for a token request, returns a response object for the appropriate response. +Authorize Controllers are the "killer feature" of OAuth2, and allow for your users to authorize +third party applications. Here is an example of an authorize controller in `authorize.php`: -`getClientCredentials` +```php +// error reporting again +ini_set('display_errors',1);error_reporting(E_ALL); - * parses the client credentials from the request and determines if they are valid +// Autoloading again +require_once('path/to/oauth2-server-php/src/OAuth2/Autoloader.php'); +OAuth2_Autoloader::register(); -### Resource Requests +// create your storage again +$storage = new OAuth2_Storage_Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password)); -Any API method requiring oauth2 authentication. The server will validate the incomming request, and then allow -the application to serve back the protected resource. +// create your server again +$server = new OAuth2_Server($storage); -**methods**: +// Add the "Authorization Code" grant type (this is required for authorization flows) +$server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage)); -`verifyResourceRequest` +$request = OAuth2_Request::createFromGlobals(); +$response = new OAuth2_Response(); - * Receives a request object for a resource request, finds the token if it exists, and returns a Boolean for whether -the incomming request is valid +// validate the authorize request +if (!$server->validateAuthorizeRequest($request, $response)) { + $response->send(); + die; +} +// display an authorization form +if (empty($_POST)) { + exit(' +<form method="post"> + <label>Do You Authorize TestClient?</label><br /> + <input type="submit" name="authorized" value="yes"> + <input type="submit" name="authorized" value="no"> +</form>'); +} +// print the authorization code if the user has authorized your client +$is_authorized = ($_POST['authorized'] === 'yes'); +$server->handleAuthorizeRequest($request, $response, $is_authorized); +if ($is_authorized) { + // this is only here so that you get to see your code in the cURL request. Otherwise, we'd redirect back to the client + $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=')+5); + exit("SUCCESS! Authorization Code: $code"); +} +$response->send(); +``` -`getAccessTokenData` +Now paste the following URL in your browser - * Takes a token string as an argument and returns the token data if applicable, or null if the token is invalid +``` +http://localhost/authorize.php?response_type=code&client_id=testclient +``` + +You will be prompted with an authorization form, and receive an authorization code upon clicking "yes" + +> Note: The Authorization Code can now be used to receive an access token from your previously +> created `token.php` endpoint. Just add the following grant type to `token.php`: +> ```php +> $server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage)); +> ``` +> And call this endpoint using the returned authorization code: +> ```bash +> curl -u testclient:testpass http://localhost/token.php -d 'grant_type=authorization_code&redirect_uri=http://fake/&code=YOUR_CODE' +> ``` Grant Types ----------- @@ -184,6 +272,10 @@ Supported grant types are as follows: The client can submit refresh token and recieve a new access token e.g. it may be necessary to do this if the access_token had expired. + 7. [Extension Grant](http://tools.ietf.org/html/rfc6749#section-4.5) + + Create your own grant type by implementing the `OAuth2_GrantTypeInterface` and adding it to the OAuth2 Server object. + When submitting a request for an access_token using either the 'Authorization Code' or 'Resource Owner Password Credential' grant, a refresh_token is provided. However, When using the refresh_token from above to request a new access_token, a new refresh_token is not provided. The spec does not strictly require a refresh_token be granted but it @@ -193,8 +285,7 @@ As a result, the option always_issue_new_refresh_token was added (defaults to FA [OAuth2_GrantType_RefreshToken](src/OAuth2/GrantType/RefreshToken.php) class. So, by default a new refresh token is not issued, but you can easily configure this to do so by setting `'always_issue_new_refresh_token' => true` -If you want to support more than one grant type it is possible to add more than 1 type to the $server as you -initialize, see below. +If you want to support more than one grant type it is possible to add more when the Server object is created: ```php $server->addGrantType(new OAuth2_GrantType_UserCredentials($storage)); @@ -202,7 +293,64 @@ $server->addGrantType(new OAuth2_GrantType_RefreshToken($storage)); $server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage)); ``` -Create a custom grant type by implementing the `OAuth2_GrantTypeInterface` and adding it to the OAuth2 Server object. +Server Methods +-------------- + +> ...an end-user (resource owner) can grant a printing +> service (client) access to her protected photos stored at a photo +> sharing service (resource server), without sharing her username and +> password with the printing service. Instead, she authenticates +> directly with a server trusted by the photo sharing service +> (authorization server), which issues the printing service delegation- +> specific credentials (access token). +> +> ~ OAuth2 ([draft #31](http://tools.ietf.org/html/rfc6749#section-1)) + +Most OAuth2 APIs will have endpoints for `Authorize Requests`, `Token Requests`, and `Resource Requests`. The `OAuth2_Server` object has methods to handle each of these requests. + +### Authorize Requests + +An endpoint requiring the user to authenticate, which redirects back to the client with an `authorization code`. + +**methods**: + +`handleAuthorizeRequest` + * Receives a request object for an authorize request, returns a response object with the appropriate response + +`validateAuthorizeRequest` + * Receives a request object, returns false if the incoming request is not a valid Authorize Request. If the request +is valid, returns an array of retrieved client details together with input. +Applications should call this before displaying a login or authorization form to the user + +### Token Requests + +An endpoint which the client uses to exchange the `authorization code` for an `access token`. + +**methods**: + +`grantAccessToken` + + * Receives a request object for a token request, returns a token if the request is valid. + +`handleTokenRequest` + + * Receives a request object for a token request, returns a response object for the appropriate response. + +### Resource Requests + +Any API method requiring oauth2 authentication. The server will validate the incomming request, and then allow +the application to serve back the protected resource. + +**methods**: + +`verifyResourceRequest` + + * Receives a request object for a resource request, finds the token if it exists, and returns a Boolean for whether +the incomming request is valid + +`getAccessTokenData` + + * Takes a token string as an argument and returns the token data if applicable, or null if the token is invalid The Response Object ------------------- @@ -211,16 +359,19 @@ The response object serves the purpose of making your server OAuth2 compliant. and response body for a valid or invalid oauth request. To use it as it's simplest level, just send the output and exit: ```php +$request = OAuth2_Request::createFromGlobals(); +$response = new OAuth2_Response(); + // will set headers, status code, and json response appropriately for success or failure -$server->grantAccessToken(); -$server->getResponse()->send(); +$server->grantAccessToken($request, $response); +$response->send(); ``` The response object can also be used to customize output. Below, if the request is NOT valid, the error is sent to the browser: ```php -if (!$token = $server->grantAccessToken()) { - $server->getResponse()->send(); +if (!$token = $server->grantAccessToken($request, $response)) { + $response->send(); die(); } echo sprintf('Your token is %s!!', $token); @@ -230,8 +381,7 @@ This will populate the appropriate error headers, and return a json error respon the response object can be used to display the information in any other format: ```php -if (!$token = $server->grantAccessToken()) { - $response = $server->getResponse(); +if (!$token = $server->grantAccessToken($request, $response)) { $parameters = $response->getParameters(); // format as XML header("HTTP/1.1 " . $response->getStatusCode()); @@ -309,10 +459,11 @@ access it: ```php // https://api.example.com/resource-requiring-postonwall-scope $request = OAuth2_Request::createFromGlobals(); +$response = new OAuth2_Response(); $scopeRequired = 'postonwall'; // this resource requires "postonwall" scope -if (!$server->verifyResourceRequest($request, $scopeRequired)) { +if (!$server->verifyResourceRequest($request, $response, $scopeRequired)) { // if the scope required is different from what the token allows, this will send a "401 insufficient_scope" error - $server->getRequest()->send(); + $response->send(); } ``` |
