REST API is working without authentication methods. Now i wanted to authenticate REST API with OAuth2 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.
Do i need to create custom OAuth 2 client like this ? Creating your own auth clients
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 user 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;
/**
* Country Controller API
*
* @author Budi Irawan <[email protected]>
*/
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
I'm using JWT for validating the request. Basically JWT is a token which also contain information about a user, and about the token itself such as the validity and the expiration time of the token. You can read more about JWT here.
The flow of my application is like this:
First, when a user logged in, create a JWT for the user
$key = base64_decode('some_random_string');
$tokenId = base64_encode(mcrypt_create_iv(32));
$issuedAt = time();
$notBefore = $issuedAt + 5;
$expire = $notBefore + 1800;
$user = User::findByEmail($email);
$data = [
'iss' => 'your-site.com',
'iat' => $issuedAt,
'jti' => $tokenId,
'nbf' => $notBefore,
'exp' => $expire,
'data' => [
'id' => $user->id,
'username' => $user->username,
//put everything you want (that not sensitive) in here
]
];
$jwt = JWT::encode($data, $key,'HS256');
return $jwt;
Then, the client (e.g the mobile app) must provide the token in every request via Authorization header. The header will look like this:
Authorization:Bearer [the JWT token without bracket]
In the User model, add a method like this for validating the token:
public static function findIdentityByAccessToken($token, $type = null) {
$key = base64_decode('the same key that used in login function');
try{
$decoded = JWT::decode($token, $key, array('HS256'));
return static::findByEmail($decoded->data->email);
}catch (\Exception $e){
return null;
}
}
The JWT library will raise an Exception if the token is no longer invalid (have been tampered or have been past the expiry time).
Then, add this to the behaviors function in every controller:
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
'except' => ['login'] //action that you don't want to authenticate such as login
];
That's it! I hope this work like you wanted. Oh, and there is lot of JWT libraries that you can use (you can see it here), but I personally use this library by people from firebase
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With