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
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(),
],
],
]
);
}