1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
|
oauth2-server-php
=================
[](http://travis-ci.org/bshaffer/oauth2-server-php)
An OAuth2.0 Server in PHP! [View the Full Working Demo](http://brentertainment.com/oauth2) ([code](https://github.com/bshaffer/oauth2-demo-php))
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`:
```php
require_once('/path/to/oauth2-server-php/src/OAuth2/Autoloader.php');
OAuth2_Autoloader::register();
```
Using [Composer](http://getcomposer.php)? Add the following to `composer.json`:
```
{
"require": {
"bshaffer/oauth2-server-php": "dev-develop",
...
},
...
}
```
And then run `composer.phar install`
> It is highly recommended you check out the [`v0.9`](https://github.com/bshaffer/oauth2-server-php/tree/v0.9) 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-demo-php), 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
-----------
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_AuthorizationCode($storage)); // or any grant type you like!
$server->handleTokenRequest(OAuth2_Request::createFromGlobals(), new OAuth2_Response())->send();
```
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-demo-php).
### Define your Schema
The quickest way to get started is to use the following schema to create the default database:
##### MySQL / SQLite / PostgreSQL / MS SQL Server
```sql
CREATE TABLE oauth_clients ( client_id VARCHAR(80) NOT NULL, client_secret VARCHAR(80) NOT NULL, redirect_uri VARCHAR(2000) NOT NULL, CONSTRAINT client_id_pk PRIMARY KEY (client_id));
CREATE TABLE oauth_access_tokens (access_token VARCHAR(40) NOT NULL, client_id VARCHAR(80) NOT NULL, user_id VARCHAR(255), expires TIMESTAMP NOT NULL,scope VARCHAR(2000), CONSTRAINT access_token_pk PRIMARY KEY (access_token));
CREATE TABLE oauth_authorization_codes (authorization_code VARCHAR(40) NOT NULL, client_id VARCHAR(80) NOT NULL, user_id VARCHAR(255), redirect_uri VARCHAR(2000) NOT NULL, expires TIMESTAMP NOT NULL, scope VARCHAR(2000), CONSTRAINT auth_code_pk PRIMARY KEY (authorization_code));
CREATE TABLE oauth_refresh_tokens ( refresh_token VARCHAR(40) NOT NULL, client_id VARCHAR(80) NOT NULL, user_id VARCHAR(255), expires TIMESTAMP NOT NULL, scope VARCHAR(2000), CONSTRAINT refresh_token_pk PRIMARY KEY (refresh_token));
CREATE TABLE oauth_users (username VARCHAR(255) NOT NULL, password VARCHAR(2000), first_name VARCHAR(255), last_name VARCHAR(255), CONSTRAINT username_pk PRIMARY KEY (username));
```
##### Oracle:
```sql
CREATE TABLE oauth_clients ( client_id VARCHAR2(80) NOT NULL, client_secret VARCHAR2(80) NOT NULL, redirect_uri VARCHAR2(2000) NOT NULL, CONSTRAINT client_id_pk PRIMARY KEY (client_id));
CREATE TABLE oauth_access_tokens (access_token VARCHAR2(40) NOT NULL, client_id VARCHAR2(80) NOT NULL, user_id VARCHAR2(255), expires TIMESTAMP NOT NULL,scope VARCHAR2(2000), CONSTRAINT access_token_pk PRIMARY KEY (access_token));
CREATE TABLE oauth_authorization_codes (authorization_code VARCHAR2(40) NOT NULL, client_id VARCHAR2(80) NOT NULL, user_id VARCHAR2(255), redirect_uri VARCHAR2(2000) NOT NULL, expires TIMESTAMP NOT NULL, scope VARCHAR2(2000), CONSTRAINT auth_code_pk PRIMARY KEY (authorization_code));
CREATE TABLE oauth_refresh_tokens ( refresh_token VARCHAR2(40) NOT NULL, client_id VARCHAR2(80) NOT NULL, user_id VARCHAR2(255), expires TIMESTAMP NOT NULL, scope VARCHAR2(2000), CONSTRAINT refresh_token_pk PRIMARY KEY (refresh_token));
CREATE TABLE oauth_users (username VARCHAR2(255) NOT NULL, password VARCHAR2(2000), first_name VARCHAR2(255), last_name VARCHAR2(255), CONSTRAINT username_pk PRIMARY KEY (username));
```
### 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);
// Add the "Client Credentials" grant type (it is the simplest of the grant types)
$server->addGrantType(new OAuth2_GrantType_ClientCredentials($storage));
// 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();
```
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/");
```
Now run the following from the command line:
```bash
curl -u testclient:testpass http://localhost/token.php -d 'grant_type=client_credentials'
```
> 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 everything works, you should receive a response like this:
```json
{"access_token":"03807cb390319329bdf6c777d4dfae9c0d3b3c35","expires_in":3600,"token_type":"bearer","scope":null}
```
### Create a Resource Controller
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`:
```php
// error reporting again
ini_set('display_errors',1);error_reporting(E_ALL);
// Autoloading again
require_once('path/to/oauth2-server-php/src/OAuth2/Autoloader.php');
OAuth2_Autoloader::register();
// create your storage again
$storage = new OAuth2_Storage_Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));
// create your server again
$server = new OAuth2_Server($storage);
// 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!'));
```
Now run the following from the command line:
```bash
curl http://localhost/resource.php -d 'access_token=YOUR_TOKEN'
```
> Note: Use the value returned in "access_token" from the previous step in place of YOUR_TOKEN
If all goes well, you should receive a response like this:
```json
{"success":true,"message":"You accessed my APIs!"}
```
### Create an Authorize Controller
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`:
```php
// error reporting again
ini_set('display_errors',1);error_reporting(E_ALL);
// Autoloading again
require_once('path/to/oauth2-server-php/src/OAuth2/Autoloader.php');
OAuth2_Autoloader::register();
// create your storage again
$storage = new OAuth2_Storage_Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));
// create your server again
$server = new OAuth2_Server($storage);
// Add the "Authorization Code" grant type (this is required for authorization flows)
$server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage));
$request = OAuth2_Request::createFromGlobals();
$response = new OAuth2_Response();
// 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();
```
Now paste the following URL in your browser
```
http://localhost/authorize.php?response_type=code&client_id=testclient&state=xyz
```
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
-----------
There are many supported grant types in the OAuth2 specification, and this library allows for the addition of custom grant types as well.
Supported grant types are as follows:
1. [Authorization Code](http://tools.ietf.org/html/rfc6749#section-4.1)
An authorization code obtained by user authorization is exchanged for a token
2. [Implicit](http://tools.ietf.org/html/rfc6749#section-4.2)
As part of user authorization, a token is retured to the client instead of an authorization code
3. [Resource Owner Password Credentials](http://tools.ietf.org/html/rfc6749#section-4.3)
The username and password are submitted as part of the request, and a token is issued upon successful authentication
4. [Client Credentials](http://tools.ietf.org/html/rfc6749#section-4.4)
The client can use their credentials to retrieve an access token directly, which will allow access to resources under the client's control
5. [JWT Authorization Grant](http://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-04#section-4)
The client can submit a JWT (JSON Web Token) in a request to the token endpoint. An access token (without a refresh token) is then returned directly.
6. [Refresh Token](http://tools.ietf.org/html/rfc6749#section-6)
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
is [still possible to do it](http://tools.ietf.org/html/rfc6749#section-6).
As a result, the option always_issue_new_refresh_token was added (defaults to FALSE) in the
[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 when the Server object is created:
```php
$server->addGrantType(new OAuth2_GrantType_UserCredentials($storage));
$server->addGrantType(new OAuth2_GrantType_RefreshToken($storage));
$server->addGrantType(new OAuth2_GrantType_AuthorizationCode($storage));
```
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
-------------------
The response object serves the purpose of making your server OAuth2 compliant. It will set the appropriate status codes, headers,
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($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($request, $response)) {
$response->send();
die();
}
echo sprintf('Your token is %s!!', $token);
```
This will populate the appropriate error headers, and return a json error response. If you do not want to send a JSON response,
the response object can be used to display the information in any other format:
```php
if (!$token = $server->grantAccessToken($request, $response)) {
$parameters = $response->getParameters();
// format as XML
header("HTTP/1.1 " . $response->getStatusCode());
header("Content-Type: text/xml");
echo "<error><name>".$parameters['error']."</name><message>".$parameters['error_description']."</message></error>";
}
```
This is very useful when working in a framework or existing codebase, where this library will not have full control of the response.
Scope
-----
####Configure your Scope
The use of Scope in an OAuth2 application is often key to proper permissioning. Scope is used to limit the authorization
granted to the client by the resource owner. The most popular use of this is Facebook's ability for users to authorize
a variety of different functions to the client ("access basic information", "post on wall", etc).
In this library, scope is handled by implementing `OAuth2_Storage_ScopeInterface`. This can be done using your own
implementation, or by taking advantage of the existing `OAuth2_Storage_Memory` class:
```php
// configure your available scopes
$defaultScope = 'basic';
$supportedScopes = array(
'basic',
'postonwall',
'accessphonenumber'
);
$memory = new OAuth2_Storage_Memory(array(
'default_scope' => $defaultScope,
'supported_scopes' => $supportedScopes
));
$scopeUtil = new OAuth2_Scope($memory);
$server->setScopeUtil($scopeUtil);
```
This is the simplest way, but scope can by dynamically configured as well:
```php
// configure your available scopes
$doctrine = Doctrine_Core::getTable('OAuth2Scope');
$scopeUtil = new OAuth2_Scope($doctrine);
$server->setScopeUtil($scopeUtil);
```
This example assumes the class being used implements `OAuth2_Storage_ScopeInterface`:
```php
class OAuth2ScopeTable extends Doctrine_Table implements OAuth2_Storage_ScopeInterface
{
public function getDefaultScope()
{
//...
}
public function scopeExists($scope, $client_id = null)
{
//...
}
}
```
####Validate your scope
Configuring your scope in the server class will ensure requested scopes by the client are valid. However, there are two
steps required to ensure the proper validation of your scope. First, the requested scope must be exposed to the resource
owner upon authorization. In this library, this is left 100% to the implementation. The UI or whathaveyou must make clear
the scope of the authorization being granted. Second, the resource request itself must specify what scope is required to
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, $response, $scopeRequired)) {
// if the scope required is different from what the token allows, this will send a "401 insufficient_scope" error
$response->send();
}
```
####Customizing your scope
As the implementation of "scope" can be significantly different for each application, providing a different class other than
OAuth2_Scope can be beneficial. Implement `OAuth2_ScopeInterface` in a custom class to fully customize.
State
-----
The `state` parameter is required by default for authorize redirects. This is the equivalent of a `CSRF` token, and provides
session validation for your Authorize request. See the [OAuth2.0 Spec](http://tools.ietf.org/html/rfc6749#section-4.1.1)
for more information on state.
This is enabled by default for security purposes, but you can remove this requirement when you configure your server:
```php
// on creation
$server = new OAuth2_Server($storage, array('enforce_state' => false));
// or after creation
$server = new OAuth2_Server();
$server->setConfig('enforce_state', false);
```
Contact
-------
The best way to get help and ask questions is to [file an issue](https://github.com/bshaffer/oauth2-server-php/issues/new). This will
help answer questions for others as well.
If for whatever reason filing an issue does not make sense, contact Brent Shaffer (bshafs <at> gmail <dot> com)
|