A
A
Alexander Ampleev2018-09-12 12:30:06
Yii
Alexander Ampleev, 2018-09-12 12:30:06

What is the best practice to validate a request via ajax?

Here I have users can subscribe to a certain post and for this there is a "subscribe" button.
The code in the view is like this:

<div id="subscribe" class="btn_subscribe" data-user-id="<?= $user->id ?>"
         data-post-id="<?= $modelPost->id ?>"
         data-user-subscribe="0">
    </div>

Accordingly, the script sends the user id and the id of the post to which he wants to subscribe.
Request code:
$('#subscribe').on('click', function (e) {

    var el = document.querySelector('#subscribe');
    var dataToServer = '';
    dataToServer += 'userID=';
    dataToServer += el.dataset.userId;
    dataToServer += '&postID=';
    dataToServer += el.dataset.postId;

    $.ajax({

        async: true,
        cache: false,
        type: "POST",
        url: url,
        data: dataToServer,
        dataType: "html",
        ifModified: true,
        timeout: 10000,

        dataFilter: function (data, type) {

            var answer = JSON.parse(data);
            console.log(answer);
        }

    });

});

The action code is simplified like this:
public function actionChangeEvent()
    {

        if (Yii::$app->request->isAjax && isset($_POST['userID']) && isset($_POST['postID'])) {

            if ($_POST['userID'] == 0) {
                return json_encode(['no user', 0]);
            }

            $user = User::findOne($_POST['userID']);
            $post = Post::findOne($_POST['postID']);

            if (isset($user) && isset($post)) {

                $alreadySubscribe = Subscribe::find()
                    ->where(['userID' => $user->id])
                    ->andWhere(['postID' => $post->id])
                    ->one();

                if (isset($alreadySubscribe)) {

                    // пользователь хочет отписаться
                    $alreadySubscribe->delete();
                    return json_encode(['removed success', Subscribe::find()->where(['postID' => $post->id])->count()]);

                } else {

                    // пользователь хочет подписаться
                    $subscribe = new Subscribe();
                    $subscribe->createdAT = date("Y-m-d H:i:s");
                    $subscribe->userID = $user->id;
                    $subscribe->postID = $post->id;
                    $subscribe->save();

                    return json_encode(['added success', Subscribe::find()->where(['postID' => $post->id])->count()]);
                }


            }

        } else {

            echo 'ошибка';

        }
    }

So, the current implementation is completely satisfied, except for security - you can easily change the user or post IDs and wind up subscriptions. To do this, you obviously need to somehow make sure that the request comes from an authorized user and that he sends his own ID in the request, and not someone else's. There are ideas, but I'm sure there are some best practices..
Yes, csrf validation is still not clear to me how it works and whether it can be used in this context to solve this problem.. All options are interesting, thanks in advance for the answers.

Answer the question

In order to leave comments, you need to log in

3 answer(s)
D
D', 2018-09-12
@Ampleev

1) You need to remove sending user id via Ajax. It doesn't matter at all.
2) user id must be taken from the current authorized user (In Yii, something like Yii::$app->user->id )

D
davidnum95, 2018-09-12
@davidnum95

$_POST['userID']
Not the best idea to get parameters directly from $_POST, there is Yii::$app->request->getBodyParams() for that.
If you have a standard implementation of authorization, then for authorized users in Yii::$app->user->id will be the id of the current user.

D
Dmitry, 2018-09-12
@slo_nik

Good afternoon.
About accessing the global array directly and getting the user id has already been written more than once.
And checking the csrf token can be done like this:
in the view

$this->registerJs('
   $("#subscribe").on("click", function(e){
        $.ajax({
              method: "POST",
              data: {id: $(this).attr("data-post-id"),_csrf: "' . Yii::$app->request->csrfToken . '"},
              success: function(data){
                console.log(data)
              }
            })
    })
', View::POS_END)

in controller
if(Yii::$app->request->isAjax){
    if(Yii::$app->request->validateCsrfToken()){
       // продолжаем выполнение кода.
    }
    else{
        return 'Error Csrf Token';
    }
}

ps
I think that lines like this $user = User::findOne($_POST['userID']);should be replaced with the
line
$user = User::findOne(['id' => Yii::$app->user->identity->id]);

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question