2

EDIT: SOLVED

Apparently this plugin, was having some problem missing the request headers. The solution was adding

SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0

To the .htaccess file to make Authorizaion variable available as this issue report says:

https://github.com/yiisoft/yii2/issues/6631

I'm currently working with Yii2 and using this OAuth2 plugin (Filsh/yii2-oauth2-server) for login and to work with tokens from a mobile HTML5 app.

I've configured everything, and it's retrieving the token, but when I try to send the token via POST it throws an error.

I start by calling send() to retrieve the token.

function send(){
    var url = "http://www.server.org/app/api/oauth2/rest/token";
    var data = {
        'grant_type':'password',
        'username':'user',
        'password':'pass',
        'client_id':'clientid',
        'client_secret':'clientsecret',
    };

    $.ajax({
        type: "POST",
        url: url,
        data: data,
        success:function(data){
        console.log(data);
            token = data.access_token;
        },
    })
};

Then when I perform this a call to createuser().

function createuser(){
        var url = "http://www.server.org/app/api/v1/users/create";
        var data = {
            'callback':'asdf',
            'username': 'user',
            'password':'pass',
            'first_name':'name',
            'last_name':'lastname'
        };

        $.ajax({
            type: "POST",
            url: url,
            data: data,
            beforeSend: function (xhr) {
                xhr.setRequestHeader('Authorization', 'Bearer ' + token);
            },
            success:function(r){
                console.log(r);
            },
        });
    }

It returns

Unauthorized: You are requesting with an invalid credential

When I change to GET instead, it works fine.

This is my controller, I'm already using:

['class' => HttpBearerAuth::className()], ['class' => QueryParamAuth::className(), 'tokenParam' => 'accessToken'],

As authentication method.

<?php

namespace app\api\modules\v1\controllers;

use Yii;
use app\models\OauthUsers;
use yii\rest\ActiveController;
use yii\web\Response;
use yii\helpers\ArrayHelper;

use yii\filters\auth\HttpBearerAuth;
use yii\filters\auth\QueryParamAuth; 
use filsh\yii2\oauth2server\filters\ErrorToExceptionFilter;
use filsh\yii2\oauth2server\filters\auth\CompositeAuth;

class UsersController extends \yii\web\Controller
{
public function behaviors()
{
    return ArrayHelper::merge(parent::behaviors(), [
        'authenticator' => [
            'class' => CompositeAuth::className(),
            'authMethods' => [
                ['class' => HttpBearerAuth::className()],
                ['class' => QueryParamAuth::className(), 'tokenParam' => 'accessToken'],
            ]
        ],
        'exceptionFilter' => [
            'class' => ErrorToExceptionFilter::className()
        ],
        'class' => \yii\filters\ContentNegotiator::className(), 

    ]);
}

/**
* Creates a new model.
* If creation is successful, the browser will be redirected to the 'view' page.
* @return mixed
*/
public function actionCreate()
{
    $model = new OauthUsers;

    try {
        if ($model->load($_POST) && $model->save()) {
            return $this->redirect(Url::previous());
        } elseif (!\Yii::$app->request->isPost) {
            $model->load($_GET);
        }
    } catch (\Exception $e) {
        $msg = (isset($e->errorInfo[2]))?$e->errorInfo[2]:$e->getMessage();
        $model->addError('_exception', $msg);
    }
    return "true";
}

}

This is my configuration object

'oauth2' => [
        'class' => 'filsh\yii2\oauth2server\Module',            
        'tokenParamName' => 'accessToken',
        'tokenAccessLifetime' => 3600 * 24,
        'storageMap' => [
            'user_credentials' => 'app\models\OauthUsers', 
        ],
        'grantTypes' => [
            'user_credentials' => [
                'class' => 'OAuth2\GrantType\UserCredentials',
            ],
            'refresh_token' => [
                'class' => 'OAuth2\GrantType\RefreshToken',
                'always_issue_new_refresh_token' => true
            ]
        ]
    ]

This is class OauthUsers

<?php

namespace app\models;

use Yii;
use \app\models\base\OauthUsers as BaseOauthUsers;

/**
* This is the model class for table "oauth_users".
*/
class OauthUsers extends BaseOauthUsers 
implements \yii\web\IdentityInterface,\OAuth2\Storage\UserCredentialsInterface

{
/**
 * @inheritdoc
 */
public static function findIdentity($id) {
    $dbUser = OauthUsers::find()
            ->where([
                "id" => $id
            ])
            ->one();
    if (!count($dbUser)) {
        return null;
    }
    return new static($dbUser);
}

/**
 * @inheritdoc
 */
public static function findIdentityByAccessToken($token, $userType = null) {

    $at = OauthAccessTokens::find()
            ->where(["access_token" => $token])
            ->one();

    $dbUser = OauthUsers::find()
            ->where(["id" => $at->user_id])
            ->one();
    if (!count($dbUser)) {
        return null;
    }
    return new static($dbUser);
}

/**
 * Implemented for Oauth2 Interface
 */
public function checkUserCredentials($username, $password)
{
    $user = static::findByUsername($username);
    if (empty($user)) {
        return false;
    }
    return $user->validatePassword($password);
}

/**
 * Implemented for Oauth2 Interface
 */
public function getUserDetails($username)
{
    $user = static::findByUsername($username);
    return ['user_id' => $user->getId()];
}

/**
 * Finds user by username
 *
 * @param  string      $username
 * @return static|null
 */
public static function findByUsername($username) {
    $dbUser = OauthUsers::find()
            ->where([
                "username" => $username
            ])
            ->one();
    if (!count($dbUser)) {
        return null;
    }
    return new static($dbUser);
}

/**
 * @inheritdoc
 */
public function getId()
{
    return $this->id;
}

/**
 * @inheritdoc
 */
public function getAuthKey()
{
    return $this->authKey;
}

/**
 * @inheritdoc
 */
public function validateAuthKey($authKey)
{
    return $this->authKey === $authKey;
}

/**
 * Validates password
 *
 * @param  string  $password password to validate
 * @return boolean if password provided is valid for current user
 */
public function validatePassword($password)
{
    return $this->password === $password;
}
}

I've changed createuser too, but still receiving a 401. I'm not sure it is passing through findIdentityByAccessToken (access_token is in a different table than oauth users, thats why I'm querying it first).

Any thoughts?

Andrés
  • 256
  • 4
  • 8
  • please, post the error –  Mar 07 '16 at 22:55
  • Its already posted. But this is the complete json: {"name":"Unauthorized","message":"You are requesting with an invalid credential.","code":0,"status":401,"type":"yii\\web\\UnauthorizedHttpException"} – Andrés Mar 08 '16 at 03:12
  • I'm looking for the code (401), ok thanks –  Mar 08 '16 at 08:33
  • Usually, this kind of error should be about not sending csrf parameter in form (POST) –  Mar 08 '16 at 08:36
  • I think, it is being sent as cookie: `Cookie:_csrf=7fb13214043dae81f560d6b577f15655f8955c61caf42224bf1d5b57eb3a7a55a%3A2%3A%7Bi%3A0%3Bs%3A5%3A%22_csrf%22%3Bi%3A1%3Bs%3A32%3A%2281928Pj8BIQYHiDD504Y0cb-yYuR_V3Q%22%3B%7D; PHPSESSID=m39veuj4oreogbsri9p6sr9367` – Andrés Mar 08 '16 at 17:16
  • Form adds the crsf as a cookie, but the error comes when crsf value is not sending through AJAX call as a parameter in POST. –  Mar 08 '16 at 17:35

2 Answers2

1

I don't know the plugin you are using but what I know is that you can use the Yii2 HttpBearerAuth filter when implementing OAuth 2.0 which means using the HTTP Bearer token. And that token is typically passed with the Authorization header of the HTTP request instead of the body request and it usually looks like :

Authorization: Bearer y-9hFW-NhrI1PK7VAXYdYukwWVrNTkQ1

The idea is about saving the token you received from the server somewhere (should be a safe place) and include it in the headers of the requests that requires server authorization (maybe better explained here, here or here) So the JS code you are using to send a POST request should look more like this :

function createuser(){
    var url = "http://www.server.org/app/api/v1/users/create";
    var data = {
        'callback':'cb',
        'username': 'user',
        'password':'pass',
        'first_name':'name',
        'last_name':'lastname'
    };

    $.ajax({
        type: "POST",
        url: url,
        data: data,
        beforeSend: function (xhr) {
            xhr.setRequestHeader('Authorization', 'Bearer ' + token);
        },
        success:function(r){
            console.log(r);
        },
    });
}

Also check in the User class (the one defined in your config files and responsible of authenticating a user) check the implementation of the findIdentityByAccessToken() method. it should usually look like this (see Yii docs for more details) :

public static function findIdentityByAccessToken($token, $type = null)
{
    return static::findOne(['auth_key' => $token]);
}

This is the function that will receive the token and it should return null when authentication should fail (by default findOne() returns null when no record is found) or returns a User instance to register it and make it accessible within Yii::$app->user->identity or Yii::$app->user->id anywhere inside your app so you can implement whatever logic you need inside it like checking the validity of an access token or its existence in a different table.

Community
  • 1
  • 1
Salem Ouerdani
  • 7,596
  • 3
  • 40
  • 52
  • Thanks Salem, this is pointing me in a new direction. Anyway, I've updated my answer, please check, im still having a 401 as response. – Andrés Mar 08 '16 at 17:02
  • try to add `exit('token : ' . $token);` or equivalent as a first line of code inside `findIdentityByAccessToken()` to see first if the token is getting there. use the network tab of any browser dev tool to see the response you are getting when sending that POST request. you should see `token : XXX` where XXX is the token you did sent if thet HttpBearerAuth filter is working fine. if not then try to use only HttpBearerAuth for test proposes like [here](http://stackoverflow.com/questions/35636966/yii2-oauth-token-validation-on-every-page/35648728#35648728) – Salem Ouerdani Mar 08 '16 at 17:37
  • one more thing. an important thing. check in the network dev tool if your browser is sending an OPTIONS request before the POST request. it may be the one getting rejected. or try with a tool like postman and see if your server is rejecting both POST and OPTIONS or only OPTIONS – Salem Ouerdani Mar 08 '16 at 17:50
0

Apparently this plugin (Filsh/yii2-oauth2-server), was having some problem missing the request headers. The solution was adding

SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0

To the .htaccess file to make Authorizaion variable available as this says:

https://github.com/yiisoft/yii2/issues/6631
Andrés
  • 256
  • 4
  • 8