Yii2 REST api bearer authentication

2019-04-16 17:00发布

问题:

I am using Yii2 framework as the backend and react js for the client side. I am trying to create REST api with HTTPBearer authentication but always get a 401 Unauthorized error. I have followed the Yii2 Rest api authentication with no success. I have also implemented findIdentityByAccessToken on user.php and access_token on my sql. My files:-

Folder structure:-

-api
    --config
      --main.php
      --main-local.php
      ...
    --modules
      --v1
       --controllers
         --CheckinsController.php

-backend
-common
-frontend ..

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',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log'],
    'modules' => [
        'v1' => [
            'basePath' => '@app/modules/v1',
            'class' => 'api\modules\v1\Module'   // here is our v1 modules
        ]
    ],
    'components' => [
        'user' => [
            'identityClass' => 'common\models\User',
            'enableAutoLogin' => false,
            'enableSession' => false,
            'loginUrl' =>'',
        ],
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
        'request' => [
            'class' => '\yii\web\Request',
            'enableCookieValidation' => false,
    'parsers' => [
        'application/json' => 'yii\web\JsonParser',
    ]
],
        'urlManager' => [
            'enablePrettyUrl' => true,
            'enableStrictParsing' => true,
            'showScriptName' => false,
            'rules' => [
                [
                    'class' => 'yii\rest\UrlRule',
                    'controller' => 'v1/user', 
                    'tokens' => [
                        '{id}' => '<id:\\w+>'
                    ]
                ],
                  [
                    'class' => 'yii\rest\UrlRule',
                    'controller' => 'v1/event', 
                    'extraPatterns' => [
                    'GET test' => 'test'
                    ],     
                ],
                [
                    'class' => 'yii\rest\UrlRule',
                    'controller' => 'v1/checkins', 
                    'extraPatterns' => [
                     'GET checkinview/<id:\d+>' => 'checkinview/'
                    ],     
                ]
            ],
        ]
    ],
    'params' => $params,
];

CheckinsController.php

namespace api\modules\v1\controllers;

use yii\rest\ActiveController;
use yii\data\ActiveDataProvider;
use yii\filters\ContentNegotiator;
use api\modules\v1\models\CheckinApi;
use yii\filters\auth\HttpBearerAuth;
use yii\web\Response;

class CheckinsController extends ActiveController
{
    public $modelClass = 'common\models\Events';

public function behaviors()
{
    $behaviors = parent::behaviors();
    $behaviors['authenticator'] = [
        'class' => HttpBearerAuth::className()   
    ];
     $behaviors['contentNegotiator'] = [
            'class' => ContentNegotiator::className(),
            'formats' => [
                'application/json' => Response::FORMAT_JSON,
            ],
        ];

    return $behaviors;
}


public function actionCheckinview($id)
{
//     \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    $query = new CheckinApi();
    $test = 
        [
            'count' => $query->Checkincount($id),
            'checkinid' => $id,
            'useridused' => Yii::$app->user->identity->id,

        ];
         return $test;//Testing purpose
}
}

User.php

class User extends ActiveRecord implements IdentityInterface
{
const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10;

/**
 * @inheritdoc
 */
public static function tableName()
{
    return '{{%user}}';
}

/**
 * @inheritdoc
 */
public function behaviors()
{
    return [
        TimestampBehavior::className(),
    ];
}

/**
 * @inheritdoc
 */
public function rules()
{
    return [
        ['status', 'default', 'value' => self::STATUS_ACTIVE],
        ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
    ];
}

public function fields()
{
    $fields = parent::fields();

    // remove fields that contain sensitive information
    unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token'],$fields['access_token']);

    return $fields;
}

/**
 * @inheritdoc
 */
public static function findIdentity($id)
{
    return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}

/**
 * @inheritdoc
 */
public static function findIdentityByAccessToken($token, $type = null)
{
     return static::findOne(['access_token' => $token]);
}

/**
 * Finds user by username
 *
 * @param string $username
 * @return static|null
 */
public static function findByUsername($username)
{
    return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
}

/**
 * Finds user by password reset token
 *
 * @param string $token password reset token
 * @return static|null
 */
public static function findByPasswordResetToken($token)
{
    if (!static::isPasswordResetTokenValid($token)) {
        return null;
    }

    return static::findOne([
        'password_reset_token' => $token,
        'status' => self::STATUS_ACTIVE,
    ]);
}

/**
 * Finds out if password reset token is valid
 *
 * @param string $token password reset token
 * @return boolean
 */
public static function isPasswordResetTokenValid($token)
{
    if (empty($token)) {
        return false;
    }
    $expire = Yii::$app->params['user.passwordResetTokenExpire'];
    $parts = explode('_', $token);
    $timestamp = (int) end($parts);
    return $timestamp + $expire >= time();
}

/**
 * @inheritdoc
 */
public function getId()
{
    return $this->getPrimaryKey();
}

/**
 * @inheritdoc
 */
public function getAuthKey()
{
    return $this->auth_key;
}

/**
 * @inheritdoc
 */
public function validateAuthKey($authKey)
{
    return $this->getAuthKey() === $authKey;
}

/**
 * Validates password
 *
 * @param string $password password to validate
 * @return boolean if password provided is valid for current user
 */
public function validatePassword($password)
{
    return Yii::$app->security->validatePassword($password, $this->password_hash);
}

/**
 * Generates password hash from password and sets it to the model
 *
 * @param string $password
 */
public function setPassword($password)
{
    $this->password_hash = Yii::$app->security->generatePasswordHash($password);
}

/**
 * Generates "remember me" authentication key
 */
public function generateAuthKey()
{
    $this->auth_key = Yii::$app->security->generateRandomString();
}

 /**
 * Generates "api" access token
 */
public function generateAccessToken()
{
    $this->access_token = Yii::$app->security->generateRandomString($length = 16);
}

/**
 * Generates new password reset token
 */
public function generatePasswordResetToken()
{
    $this->password_reset_token = Yii::$app->security-       >generateRandomString() . '_' . time();
}

/**
 * Removes password reset token
 */
public function removePasswordResetToken()
{
    $this->password_reset_token = null;
}
}

Any help would be much appreciated!! trying to get around this problem for days, but no success. Dont know if it is a simple error done by me!!

回答1:

  • Give me "user.php" for checking more...
  • The "CheckinsController" should like below LOCs (don't add more information when you don't control it).

    namespace api\modules\v1\controllers;

    use yii\rest\ActiveController;
    use yii\data\ActiveDataProvider;
    use yii\filters\ContentNegotiator;
    use api\modules\v1\models\CheckinApi;
    use yii\filters\auth\HttpBearerAuth;
    use yii\web\Response;

    class CheckinsController extends ActiveController
    {
        public $modelClass = 'common\models\Events';

    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator'] = [
            'class' => HttpBearerAuth::className()   
        ]; 
        return $behaviors;
    } 
    }
  • You can refer a manual here: http://budiirawan.com/setup-restful-api-yii2/
  • Also can refer this project https://github.com/NguyenDuyPhong/yii2_advanced_api_phong (including database) - It's working very well on my localhost. Hopefully I helped you something!


回答2:

 public function behaviors() {
    $behaviors = parent::behaviors();
    $behaviors['authenticator'] = [
        'class' => CompositeAuth::className(),
        'except' => ['token'],
        'authMethods' => [
            HttpBearerAuth::className(),
            QueryParamAuth::className(),
        ],
    ];
    return $behaviors;
}


回答3:

I had the same case you did. I am using ReactJS for client dan Yii2 for api.

In your case, check this rule:

[
    'class' => 'yii\rest\UrlRule',
    'controller' => 'v1/checkins', 
    'extraPatterns' => [
     'GET checkinview/<id:\d+>' => 'checkinview/'
    ],     
]

This code should be:

[
    'class' => 'yii\rest\UrlRule',
    'controller' => 'v1/checkins', 
    'tokens' => ['{id}' => '<id:\\w+>'], --> because you stil use ActiveController
    'pluralize' => false, --> for disable pluralize
    'extraPatterns' => [
     'GET checkinview/<id:\d+>' => 'checkinview' --> remove '/' sign
     'OPTIONS checkinview/<id:\d+>' => 'options', --> for corsFilter
    ],     
]


回答4:

In my case, when checking on firebug, there are two calls made, one GET and one OPTIONS. The GET will return 200 OK and WITHOUT response, while the OPTIONS will return 401 Unauthorized access.

When browsing in google, found out that this simple line will make it work.

public function behaviors() {
    return
            \yii\helpers\ArrayHelper::merge(parent::behaviors(), [
                'corsFilter' => [
                    'class' => \yii\filters\Cors::className(),
                ],
                'authenticator' => [
                    'class' => \yii\filters\auth\HttpBearerAuth::className(),
                    'except' => ['options'],
                ],
    ]);
}

But, I do not understand why OPTIONS, why the GET return no response? Can anyone elaborate more on this?