3

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!!

prasad mohan
  • 31
  • 1
  • 1
  • 4

4 Answers4

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;
    } 
    }
  • Thanks for your time and response, would give it a shot. And i have also included user.php. Do check it out.I am using generateAccessToken method for api – prasad mohan Aug 31 '15 at 17:18
1

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
    ],     
]
m00am
  • 5,910
  • 11
  • 53
  • 69
  • `'OPTIONS checkinview/' => 'options', --> for corsFilter` if I am missing this will it show status code as 401 ? Unauthenticated whereas the same request works fine in PostMan – Harshit Chaudhary Apr 21 '17 at 09:09
  • 1
    its for [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS), if you using axios or fetch js for HTTP promise, `OPTIONS` request is used to check if you are allowed to perform the `GET, POST, DELETE, UPDATE`. jQuery use `application/x-www-form-urlencoded` while axios or fetch use `application/json`. please check [this](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Simple_requests) – muhammad aser Apr 23 '17 at 02:41
0
 public function behaviors() {
    $behaviors = parent::behaviors();
    $behaviors['authenticator'] = [
        'class' => CompositeAuth::className(),
        'except' => ['token'],
        'authMethods' => [
            HttpBearerAuth::className(),
            QueryParamAuth::className(),
        ],
    ];
    return $behaviors;
}
Josimar Andrade
  • 111
  • 1
  • 7
-1

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?

Daniel Adinugroho
  • 1,399
  • 2
  • 11
  • 21