Chanuka Asanka
Chanuka Asanka

Reputation: 3014

Yii 2 RESTful API authenticate with OAuth2 (Yii 2 advanced template)

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 <deerawan@gmail.com>
 */
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

Upvotes: 9

Views: 7795

Answers (3)

Luqman Sungkar
Luqman Sungkar

Reputation: 173

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

Upvotes: 6

Ahmad Yazji
Ahmad Yazji

Reputation: 566

You need to do the following:

  • set the token before saving the user in the User model.
  • add actionLogin in the UserController to return the auth_key on user login.
  • in each API request you send the auth_key in the header instead of sending username and password.
  • to check if the auth_key is valid, define 'authenticator' in the UserController behaviors.

you can find code samples in my answer to another question here

Upvotes: 1

Vahe Galstyan
Vahe Galstyan

Reputation: 1729

You Can create Your Auth System, usually I do it. You can Save Tokens for every user, and after it authentify user by that token. In every action You can send that token for authentify user.

Upvotes: 1

Related Questions