REST API Guide
CyberPunkCodes opened this issue · 4 comments
One of the issues I have just ran into, is using Yii2 to create a REST API. The official docs lack in this area (and many others). It takes into account, assumptions, that you know where things go. While I have used Yii2 for a while, it still would be nice to get the full story, instead of bits and pieces and having to find the rest. The docs say "Implement xxx", but doesn't tell you how, so we have to fill in the gaps. My experience with Yii helps me find these answers quickly, but beginners will get stuck on these things for days.
Your cookbook is helpful, and shows great examples of use-cases and a "how-to" for specific things. Which is why I think it would be great if you could add a full walk through on setting up a REST API, without the gaps of assuming everyone knows what they are doing. Assuming that they know nothing about Yii :)
While I just got a QueryParamAuth finally working, it was a pain. After following various guides (including official Yii docs), it leaves out a lot of necessary info. I will start with what I have so far.
I copied the "frontend" app, renamed it to "api", and removed unnecessary things like the "views" folder.
Here is a tree view:
[api]
- [config]
- - bootstrap.php
- - main-local.php
- - main.php
- - params-local.php
- - params.php
- [modules]
- - [v1]
- - - [components]
- - - -ApiController.php - (extends \yii\rest\ActiveController)
- - - [controllers]
- - - - UserController.php - (extends ApiController)
- - - [models]
- - - - User.php - (sets tableName, primaryKey, rules)
- - - Module.php
api/config/main.php
<?php
$params = array_merge(
require(__DIR__ . '/../../common/config/params.php'),
require(__DIR__ . '/../../common/config/params-local.php'),
require(__DIR__ . '/params.php'),
require(__DIR__ . '/params-local.php')
);
return [
'id' => 'app-api'
'name' => 'My Yii2 API',
'basePath' => dirname(__DIR__),
'controllerNamespace' => 'api\controllers',
'bootstrap' => ['log'],
'modules' => [
'v1' => [
'basePath' => '@app/modules/v1',
'class' => 'api\modules\v1\Module'
]
],
'components' => [
'user' => [
'identityClass' => 'common\models\User',
'enableSession' => false,
'loginUrl' => null,
'enableAutoLogin' => false,
],
'request' => [
'class' => '\yii\web\Request',
'enableCookieValidation' => false,
'parsers' => [
'application/json' => 'yii\web\JsonParser',
]
],
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => 'v1/user',
]
],
],
],
'params' => $params,
];
api/modules/v1/components/ApiController.php
<?php
namespace api\modules\v1\components;
use yii\filters\auth\QueryParamAuth;
use yii\filters\Cors;
/**
* API Base Controller
* All controllers within API app must extend this controller!
*/
class ApiController extends \yii\rest\ActiveController
{
public function behaviors()
{
$behaviors = parent::behaviors();
// add CORS filter
$behaviors['corsFilter'] = [
'class' => Cors::className(),
];
// add QueryParamAuth for authentication
$behaviors['authenticator'] = [
'class' => QueryParamAuth::className(),
];
// avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
$behaviors['authenticator']['except'] = ['options'];
return $behaviors;
}
}
api/modules/v1/controllers/UserController.php
<?php
namespace api\modules\v1\controllers;
/**
* User Controller
*/
class UserController extends \api\modules\v1\components\ApiController
{
public $modelClass = 'api\modules\v1\models\User';
}
api/modules/v1/models/User.php
<?php
namespace api\modules\v1\models;
use \yii\db\ActiveRecord;
/**
* User Model
*/
class User extends ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'user';
}
/**
* @inheritdoc
*/
public static function primaryKey()
{
return ['id'];
}
/**
* Define rules for validation
*/
public function rules()
{
return [
[['username', 'email', 'password_hash'], 'required']
];
}
}
api/modules/v1/Module.php
<?php
namespace api\modules\v1;
class Module extends \yii\base\Module
{
public $controllerNamespace = 'api\modules\v1\controllers';
public function init()
{
parent::init();
}
}
Then lastly, outside of the API app, in "common" directory, modify /common/models/User.php
to handle the access_token
/**
* Finds an identity by the given token.
*
* @param string $token the token to be looked for
* @return IdentityInterface|null the identity object that matches the given token.
*/
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['access_token' => $token, 'status' => self::STATUS_ACTIVE]);
}
Now, it still doesn't work. You have to manually put in an access_token
in order for it to work. I grabbed it from another guide:
-
Either manually create a new column, or create a migration
./yii migrate/create update_user_table
Replace with these 2 functions:
public function up()
{
$this->addColumn('{{%user}}', 'access_token', $this->string()->unique()->after('auth_key'));
}
public function down()
{
$this->dropColumn('{{%user}}', 'access_token');
}
- Then manually set
access_token
for the user in your db to: 4p9mj82PTl1BWSya7bfpU_Nm8u07hkcB
Now you can call it using PostMan: http://api.mydomain.dev/v1/users?access-token=4p9mj82PTl1BWSya7bfpU_Nm8u07hkcB
NOTE: I have mapped api.mydomain.dev as a VHOST to /path/to/yii2site/api/web
Now what? There is no guidance on how to set the access_token
for the users, or how to revoke it. I assume this would be done on login. I can do this, but others may not be able to! You can create a login process for your API, but also could set it during normal frontend
login, and use the API for filling the GridView and DetailView.
What about revoking the token? If you look at the findByPasswordResetToken() function, it splits the token and checks the time stamp (second half after the underscore). The findIdentityByAccessToken()
(As shown in Yii2 docs) doesn't show any verification on the token itself. You don't want the token to live forever unchecked!
I think there needs to be a full step-by-step guide on how to properly setup a REST API, and go further by showing how to login, validate the token, handle invalid token (ie: session expired), logout (removing the token). Maybe split into a few parts to handle the different types: Query, BasicAuth, Custom, etc.
@WadeShuler
I think you can create PR with your vision about that, and samdark helps with implementing this information to cookbook.
Yes, that would be good.
any updates on this issue when will we be having the proper and complete guide for the REST API
When someone contributes. I'm too busy with Yii 3.0 currently.