Yii 1.1: HOW TO: Use token based authentication

9 followers

After reading the other wiki article regarding token based access, I thought that it might be interesting to share my method.

I have extended CWebUser (which I call YWebUser) for token based access.

In my particular implementation, the token is an encrypted value which is related to a context. Each context has its own encryption key. The context index is shown in clear, and the token is encrypted information. The decrypted information is in JSON format. That information has at least:

  • t - time information which allows limitation of the token in time;
  • id - The user id

The way I make it work is:

  • The client application gets a token from the server based on login credentials. This can be a third party server.
  • The token provided by the server is encrypted JSON data with time information (t) and user id information (id). The time field (t) ensures that the token changes over time and that the web services can check its age.
  • The advantage over the database approach is that no database is required (so no specific token management) and that a third party can generate the token (shared key).

The code below references "Context::getJSONFromEncryptedTokenToArray()". Context is my CActiveRecord model for the context, and "getJSONFromEncryptedTokenToArray()" gets the context id and token from the HTTP_REQUEST (using getParam) directly. You can replace this method with any method you like for token validation (so you could still get the token from a database).

If the token is not provided, regular cookie checking takes place.

This method also allows signing in to the application (front office) using a token encrypted by a third party.

UserIdentity::getRemote($data['context'],$remote_user_id);

in the code below converts the "remote_id" provided in the token to the local id. You may not need this conversion. In my application, I allow serveral online shops to log in to my platform (including new users). So you might have a user_id 10 in all online shops which in fact corresponds to a different user each time. So in the Yii application a new user id is created and a database table makes the link between (context_id,remote_user_id) and (user_id). The 'getRemote' method gets the local user_id (and creates it if needed).

The timestamp is also checked.

The code below has been copy/pasted from my app, but does require you to make some modifications depending on your platform (getRemote and getJSONFromEncryptedTokenToArray).

class YWebUser extends CWebUser {
 
    public function init() {
        $token=Yii::app()->request->getParam('token',null);
        $context_id=Yii::app()->request->getParam('c',null);
 
        if($token===null||$context_id===null) {
            // No token or no context: use regular method:
            //  -> Parent checks cookie.
            parent::init();
        } else {
            // Check if token is valid.
            $result=$this->checkLoginToken($context_id,$token);
            if($result!==null) {
                echo CJSON::encode($result);
                exit;
            }
        }
    }
 
    const ERR_INVALID_TOKEN = -1;
    const ERR_BAD_TOKEN = -2;
    const ERR_EXPIRED_TOKEN = -3;
    const ERR_INVALID_USER = -4;
 
    const TOKEN_EXPIRE_SECONDS = 86400;
 
    private function checkLoginToken() {
        $result=array();
        $data=Context::getJSONFromEncryptedTokenToArray();
        if(!isset($data['context'])||($data['context']===null)) {
            $result['error']=self::ERR_INVALID_TOKEN;
            if(Yii::app()->request->getParam('token',null)==='(null)') {
                // Reply to iPhone version with text error in this case.
                $result['error']="ERR_INVALID_TOKEN";
            }
            $result['errmsg']=Yii::t('app','Invalid token');
        } else {
            if(!(isset($data['id']) && isset($data['t']))) {
                $result['error']=self::ERR_BAD_TOKEN;
                $result['errmsg']=Yii::t('app','Bad token');
            } else {
                $remote_user_id=intval($data['id']);
                $timestamp=intval($data['t']);
                $user=UserIdentity::getRemote($data['context'],$remote_user_id);
                if($user===null) {
                    $result['error']=self::ERR_INVALID_USER;
                    $result['errmsg']=Yii::t('app','Invalid user');
                } else {
                    if($timestamp<time()-self::TOKEN_EXPIRE_SECONDS) {
                        $result['error']=self::ERR_EXPIRED_TOKEN;
                        $result['errmsg']=Yii::t('app','Expired token');
                    } else {
                        // Timestamp and user are ok.
                        $this->login($user,0); // Only login for the current request.
                        $result=null;
                    }
                }
            }
        }
        return $result;
    }
}

as the class is overloaded, you have to update your Yii configuration in a way similar to this:

// application components
        'components'=>array(
                'user'=>array(
                        'class'=>'YWebUser',
                        /* enable cookie-based authentication */
                        'allowAutoLogin'=>true,
                        'loginUrl' => array('access/login'),
                        //'loginRequiredAjaxResponse'=>'{"error":403,"message":"User not authenticated"}',
                ),
         [...]

Total 2 comments

#18776 report it
le_top at 2015/01/05 03:26am
Reply
  1. As indicated 'getRemote()' is a method to determine a local id from a remote id. If you get an error on this method, then you have defined 'context' in the token. Therefore, it is supposed that this is a login from a remote location and the 'getRemote()' method is checked. To repeat: an online shop has its own user id numbering. In the Yii application, there is another numbering. So you need a table making the relation between the remote (shop) id and the local (Yii) id. In the example, there are several remote locations which I named 'contexts'. So I need a (context,shop_user_id) to determine the local Yii id. You can change the code if you do not have remote contexts and remove anything related to contexts.

  2. '$this->login($user,0);' is commented with 'Only login for the current request'. Calling this method actually logins the user (method from CWebUser). The second parameter is the '$duration'. By setting it to '0', autologin is disabled and therefore the token must be provided on every request.

#18775 report it
Dhara at 2015/01/05 12:29am
getRemote() method has error

First Thanx for Your Example. I have applied your code in my application. But i have error " Call to a member function getId()" in following function

UserIdentity::getRemote($data['context'],$remote_user_id);

Also Please can you specified getRemote function why used?

And

$this->login($user,0);

what is the use of above line of code.

Thanx in Advance.

Leave a comment

Please to leave your comment.

Write new article