10

REST API is working without authentication methods. Now i wanted to authenticate REST API with HTTP Basic authentication for API requests via mobile application. I tried with yii2 guide, but it didn't work for me.

basically mobile user need to be login with username & password, if a username and password are correct, user need to be login and further API request need to be validate with token.

when i debug findIdentityByAccessToken() function $token equal to username. Postman extension used for check HTTP Basic requests. access_token field in user table is empty. do i need to save it manually ? how to return access_token as a respond?

is there any reason for all three methods(HttpBasicAuth, HttpBearerAuth, QueryParamAuth) at once, why? how?

my application folder structure looks like below.

api
-config
-modules
--v1
---controllers
---models
-runtime
-tests
-web

backend
common
console
environments
frontend

api\modules\v1\Module.php

namespace api\modules\v1;
class Module extends \yii\base\Module
{
    public $controllerNamespace = 'api\modules\v1\controllers';

    public function init()
    {
        parent::init(); 
        \Yii::$app->user->enableSession = false;       
    }   
}

api\modules\v1\controllers\CountryController.php

namespace api\modules\v1\controllers;
use Yii;
use yii\rest\ActiveController;
use common\models\LoginForm;
use common\models\User;
use yii\filters\auth\CompositeAuth;
use yii\filters\auth\HttpBasicAuth;
use yii\filters\auth\HttpBearerAuth;
use yii\filters\auth\QueryParamAuth;

class CountryController extends ActiveController
{
    public $modelClass = 'api\modules\v1\models\Country';    

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

}

common\models\User.php

namespace common\models;

use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;

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

    public static function tableName()
    {
        return '{{%user}}';
    }

    public function behaviors()
    {
        return [
            TimestampBehavior::className(),
        ];
    }

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

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

    public static function findIdentityByAccessToken($token, $type = null)
    {

        return static::findOne(['access_token' => $token]);
    }


}

user table

id
username
auth_key
password_hash
password_reset_token
email
status
created_at
access_token

access_token was added after migrate user table

1

1 Answer 1

19

You need to set the token before saving the user. in the User model use this

public function beforeSave($insert)
{
    if (parent::beforeSave($insert)) {
        if ($this->isNewRecord) {
            $this->auth_key = Yii::$app->getSecurity()->generateRandomString();
        }
        return true;
    }
    return false;
}

now you have an auth_key for each user

to return the auth_key you need to add actionLogin in the UserController

public function actionLogin()
{
    $post = Yii::$app->request->post();
    $model = User::findOne(["email" => $post["email"]]);
    if (empty($model)) {
        throw new \yii\web\NotFoundHttpException('User not found');
    }
    if ($model->validatePassword($post["password"])) {
        $model->last_login = Yii::$app->formatter->asTimestamp(date_create());
        $model->save(false);
        return $model; //return whole user model including auth_key or you can just return $model["auth_key"];
    } else {
        throw new \yii\web\ForbiddenHttpException();
    }
}

after that, in each API request you send the auth_key in the header instead of sending username and password

$ curl -H "Authorization: Basic bd9615e2871c56dddd8b88b576f131f51c20f3bc" API_URL

to check if the auth_key is valid, define 'authenticator' in the UserController behaviors. (don't forget to to exclude 'create', 'login', 'resetpassword' from the authentication)

public function behaviors()
{
    return ArrayHelper::merge(
        parent::behaviors(), [
            'authenticator' => [
                'class' => CompositeAuth::className(),
                'except' => ['create', 'login', 'resetpassword'],
                'authMethods' => [
                    HttpBasicAuth::className(),
                    HttpBearerAuth::className(),
                    QueryParamAuth::className(),
                ],
            ],
        ]
    );
}
Sign up to request clarification or add additional context in comments.

1 Comment

what about the refresh token how are we gonna specify the expiration of token and then refresh if you can add that part it would be the more solid base for anyone to implement using this answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.