From 7a04dbaae17ce5b0416c78a8ca75706f6b11bbb3 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 3 Mar 2014 10:18:39 -0500 Subject: [PATCH] Support for other auth types. --- apps/advanced/common/models/User.php | 4 +- apps/basic/models/User.php | 10 ++-- docs/guide/authentication.md | 4 +- docs/guide/rest.md | 71 ++++++++++++++++++++++++---- framework/rest/Controller.php | 35 ++++++++++++-- framework/web/IdentityInterface.php | 2 +- framework/web/User.php | 2 +- 7 files changed, 103 insertions(+), 25 deletions(-) diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php index 083cecf8be..d2c80c0b46 100644 --- a/apps/advanced/common/models/User.php +++ b/apps/advanced/common/models/User.php @@ -75,9 +75,9 @@ class User extends ActiveRecord implements IdentityInterface /** * @inheritdoc */ - public static function findIdentityByToken($token) + public static function findIdentityByAccessToken($token) { - throw new NotSupportedException('"findIdentityByToken" is not implemented.'); + throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); } /** diff --git a/apps/basic/models/User.php b/apps/basic/models/User.php index 8d008a88fb..7a6fcc0048 100644 --- a/apps/basic/models/User.php +++ b/apps/basic/models/User.php @@ -8,7 +8,7 @@ class User extends \yii\base\Object implements \yii\web\IdentityInterface public $username; public $password; public $authKey; - public $apiKey; + public $accessToken; private static $users = [ '100' => [ @@ -16,14 +16,14 @@ class User extends \yii\base\Object implements \yii\web\IdentityInterface 'username' => 'admin', 'password' => 'admin', 'authKey' => 'test100key', - 'apiKey' => '100-apikey', + 'accessToken' => '100-token', ], '101' => [ 'id' => '101', 'username' => 'demo', 'password' => 'demo', 'authKey' => 'test101key', - 'apiKey' => '101-apikey', + 'accessToken' => '101-token', ], ]; @@ -38,10 +38,10 @@ class User extends \yii\base\Object implements \yii\web\IdentityInterface /** * @inheritdoc */ - public static function findIdentityByToken($token) + public static function findIdentityByAccessToken($token) { foreach (self::$users as $user) { - if ($user['apiKey'] === $token) { + if ($user['accessToken'] === $token) { return new static($user); } } diff --git a/docs/guide/authentication.md b/docs/guide/authentication.md index 5616790a0c..f43ff0d752 100644 --- a/docs/guide/authentication.md +++ b/docs/guide/authentication.md @@ -30,9 +30,9 @@ class User extends ActiveRecord implements IdentityInterface * @param string $token the token to be looked for * @return IdentityInterface|null the identity object that matches the given token. */ - public static function findIdentityByToken($token) + public static function findIdentityByAccessToken($token) { - return static::find(['api_key' => $token]); + return static::find(['access_token' => $token]); } /** diff --git a/docs/guide/rest.md b/docs/guide/rest.md index f00ccd7a65..67c958c6cf 100644 --- a/docs/guide/rest.md +++ b/docs/guide/rest.md @@ -10,7 +10,7 @@ In particular, Yii provides support for the following aspects regarding RESTful * Proper formatting of collection data and validation errors; * Efficient routing with proper HTTP verb check; * Support `OPTIONS` and `HEAD` verbs; -* Authentication via HTTP basic; +* Authentication; * Authorization; * Caching via `yii\web\HttpCache`; * Support for HATEOAS: TBD @@ -27,22 +27,22 @@ Let's use a quick example to show how to build a set of RESTful APIs using Yii. Assume you want to expose the user data via RESTful APIs. The user data are stored in the user DB table, and you have already created the ActiveRecord class `app\models\User` to access the user data. -First, check your `User` class for its implementation of the `findIdentityByToken()` method. +First, check your `User` class for its implementation of the `findIdentityByAccessToken()` method. It may look like the following: ```php class User extends ActiveRecord { ... - public static function findIdentityByToken($token) + public static function findIdentityByAccessToken($token) { - return static::find(['api_key' => $token]); + return static::find(['access_token' => $token]); } } ``` -This means your user table has a column named `api_key` which stores API access keys for the users. -Pick up a key from the table as you will need it to access your APIs next. +This means your user table has a column named `access_token` which stores API access tokens for the users. +Pick up a token from the table as you will need it to access your APIs next. Second, create a controller class `app\controllers\UserController` as follows, @@ -86,7 +86,7 @@ for accessing the user data. The APIs you have created include: You may access your APIs with the `curl` command like the following, ``` -curl -i -u "Your-API-Key:" -H "Accept:application/json" "http://localhost/users" +curl -i -u "Your-API-Access-Token:" -H "Accept:application/json" "http://localhost/users" ``` which may give the following output: @@ -108,7 +108,7 @@ Content-Type: application/json; charset=UTF-8 ``` > Tip: You may also access your API via Web browser. You will be asked -> to enter a username and password. Fill in the username field with the API key you obtained +> to enter a username and password. Fill in the username field with the API access token you obtained > previously and leave the password field blank. Try changing the acceptable content type to be `application/xml`, and you will see the result @@ -139,4 +139,57 @@ class User extends ActiveRecord In the following subsections, we will explain in more details about implementing RESTful APIs. -TBD + +HTTP Status Code Summary +------------------------ + +* `200`: OK. Everything worked as expected. +* `201`: A data item was successfully created. Please check the `Location` header for the URL to access the new data item. +* `204`: A data item was successfully deleted. +* `304`: Data not modified. You can use cached data. +* `400`: Bad request. This could be caused by various reasons from the user side, such as invalid content type request, + invalid API version number, or data validation failure. If it is data validation failure, please check + the response body for error messages. +* `401`: No valid API access token is provided. +* `403`: The authenticated user is not allowed to access the specified API endpoint. +* `404`: The requested item does not exist. +* `405`: Method not allowed. Please check the `Allow` header for allowed HTTP methods. +* `500`: Internal server error. + + +Data Formatting +--------------- + + +Implementing New API Endpoints +------------------------------ + + +Routing +------- + + +Authentication +-------------- + + +Authorization +------------- + + +Versioning +---------- + + +Caching +------- + + +Rate Limiting +------------- + +Documentation +------------- + +Testing +------- diff --git a/framework/rest/Controller.php b/framework/rest/Controller.php index 54cef01e63..941fa575f0 100644 --- a/framework/rest/Controller.php +++ b/framework/rest/Controller.php @@ -32,6 +32,14 @@ class Controller extends \yii\web\Controller * The name of the header parameter representing the API version number. */ const HEADER_VERSION = 'version'; + /** + * HTTP Basic authentication. + */ + const AUTH_TYPE_BASIC = 'Basic'; + /** + * HTTP Bearer authentication (the token obtained through OAuth2) + */ + const AUTH_TYPE_BEARER = 'Bearer'; /** * @var string|array the configuration for creating the serializer that formats the response data. @@ -41,6 +49,14 @@ class Controller extends \yii\web\Controller * @inheritdoc */ public $enableCsrfValidation = false; + /** + * @var string the authentication type. This should be a valid HTTP authentication method. + */ + public $authType = self::AUTH_TYPE_BASIC; + /** + * @var string the authentication realm to display in case when authentication fails. + */ + public $authRealm = 'api'; /** * @var string the chosen API version number * @see supportedVersions @@ -150,15 +166,24 @@ class Controller extends \yii\web\Controller /** * Authenticates the user. - * This method implements the user authentication based on HTTP basic authentication. + * This method implements the user authentication based on an access token sent through the `Authorization` HTTP header. * @throws UnauthorizedHttpException if the user is not authenticated successfully */ protected function authenticate() { - $apiKey = Yii::$app->getRequest()->getAuthUser(); - if ($apiKey === null || !Yii::$app->getUser()->loginByToken($apiKey)) { - Yii::$app->getResponse()->getHeaders()->set('WWW-Authenticate', 'Basic realm="api"'); - throw new UnauthorizedHttpException($apiKey === null ? 'Please provide an API key.' : 'You are requesting with an invalid API key.'); + $request = Yii::$app->getRequest(); + if ($this->authType == self::AUTH_TYPE_BASIC) { + $accessToken = $request->getAuthUser(); + } else { + $authHeader = $request->getHeaders()->get('Authorization'); + if ($authHeader !== null && preg_match("/^{$this->authType}\\s+(.*?)$/", $authHeader, $matches)) { + $accessToken = $matches[1]; + } + } + + if (empty($accessToken) || !Yii::$app->getUser()->loginByToken($accessToken)) { + Yii::$app->getResponse()->getHeaders()->set("WWW-Authenticate', '{$this->authType} realm=\"{$this->authRealm}\""); + throw new UnauthorizedHttpException(empty($accessToken) ? 'Access token required.' : 'You are requesting with an invalid access token.'); } } diff --git a/framework/web/IdentityInterface.php b/framework/web/IdentityInterface.php index bf3308cc72..2aac17f1fc 100644 --- a/framework/web/IdentityInterface.php +++ b/framework/web/IdentityInterface.php @@ -58,7 +58,7 @@ interface IdentityInterface * Null should be returned if such an identity cannot be found * or the identity is not in an active state (disabled, deleted, etc.) */ - public static function findIdentityByToken($token); + public static function findIdentityByAccessToken($token); /** * Returns an ID that can uniquely identify a user identity. * @return string|integer an ID that uniquely identifies a user identity. diff --git a/framework/web/User.php b/framework/web/User.php index 36900f56b1..4704856ef1 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -213,7 +213,7 @@ class User extends Component { /** @var IdentityInterface $class */ $class = $this->identityClass; - $identity = $class::findIdentityByToken($token); + $identity = $class::findIdentityByAccessToken($token); $this->setIdentity($identity); return $identity; }