0

I wanna send a post request from a unity game to a laravel 5.4 controller.. in html form, we use {{csrf_field}} and it handles creating token. but how can I do it in unity?

3 Answers3

2

I came across this thread whilst trying to acheive what it set out to do - and I got it working so I am sharing this:

It is a combination of this tutorial on YouTube: How to Use UnityWebRequest - Replacement for WWW Class - Unity Tutorial and this answer on a similar question: UnityWebRequest POST to PHP not work

The technology I am using:

  • Laravel Framework 7.15.0 (PHP)
  • Unity: 2019.3.11f1 (script is C#)

Now I am just sending basic forms not images etc - so I haven't tried anything complex but this sends a get to one URL on Laravel which returns the CSRF token.

This is then added as an additional field to the post request.

I am clicking the 2 buttons pretty quickly - I would suggest (and will implement this myself later) that you don't ask for the token until your ready to submit the form - to avoid it expiring if your form is quite long etc.

The C# Script (Unity)

Both the functions are set as ON Click() on the buttons in a simple UI Canvas in Unity.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class BasicWebCall : MonoBehaviour
{
    public Text messageText;
    public InputField scoreToSend;
    private string csrf_token;

    readonly string getURL = "http://mazegames.test/leaderboard/1";
    readonly string postURL = "http://mazegames.test/register/1";

    private void Start()
    {
        messageText.text = "Press buttons to interact with web server";
    }

    public void OnButtonGetScore()
    {
        messageText.text = "Getting Token";
        StartCoroutine(SimpleGetRequest());
    }

    IEnumerator SimpleGetRequest()
    {
        UnityWebRequest www = UnityWebRequest.Get(getURL);

        yield return www.SendWebRequest();

        if(www.isNetworkError || www.isHttpError)
        {
            Debug.LogError(www.error);
        }
        else
        {
            messageText.text = "Token Received: " + www.downloadHandler.text;
            csrf_token = www.downloadHandler.text;
        }
    }

    public void OnButtonSendScore()
    {
        if (scoreToSend.text == string.Empty)
        {
            messageText.text = "Error: No high score to send.\nEnter a value in the input field.";
        }
        else
        {
            messageText.text = "Sending Post Request";
            StartCoroutine(SimplePostRequest(scoreToSend.text));
        }
    }

    IEnumerator SimplePostRequest(string curScore)
    {

        Dictionary<string, string> wwwForm = new Dictionary<string, string>();
        wwwForm.Add("_token", csrf_token);
        UnityWebRequest www = UnityWebRequest.Post(postURL, wwwForm);
        yield return www.SendWebRequest();

        if (www.isNetworkError || www.isHttpError)
        {
            Debug.LogError(www.error);
        }

        else
        {
            messageText.text = www.downloadHandler.text;
        }
    }
}

Laravel routes/web.php

Obviously you need to configure your end points to receive your data, validate and store it if necessary. In this example its just validating the token as part of the post request.

Route::post('register/{game_id}', function($game_id) {
    return "Game On!";
});

Route::get('leaderboard/{game_id}', function($game_id) {
    return csrf_token();
});

And that is pretty much it - hope this helps someone else.

#EDIT# - Request Token on Submission

To only get the token when your submitting the form, literally all you have to do is put this line:

StartCoroutine(SimpleGetRequest());

above the line:

StartCoroutine(SimplePostRequest(scoreToSend.text));

so that is looks like this:

StartCoroutine(SimpleGetRequest());
StartCoroutine(SimplePostRequest(scoreToSend.text));

Obviously you could then remove the function SimpleGetRequest altogether.

1

Laravel will generate a token each time a page is generated. The token has a lifetime and after that lifetime it cannot be used anymore (that's the whole point).

You need to get a valid token from Laravel pass it to Unity3D and then when from Unity create a WWWForm and pass it back.

How to do this it depends on the platform that Unity3D is deployed to. If you are using WebPlayer or WebGL then you can get your hand on the Unity3D objected embedded in the browser and use SendMessage. WebGL link here.

If the game is deployed to another platform it probably makes sense to expose and API on the Laravel side and use that endpoint instead of doing a POST request.

Radu Diță
  • 13,476
  • 2
  • 30
  • 34
  • tnx for your description about token. its gonna be for android and ios.. I think you mean that I do it by GET request. of course it works with GET. I tried POST because of its more security. by the way I got token by a GET request and attached it to the wwwform. but the same token mismatch happens! because as you mentioned, token is generated as each request comes... – Armin Javid Feb 17 '17 at 11:56
  • you'll have to duplicate a lot of the browser's behaviour to make this work, at least keeping and sending cookies so you overcome csrf protection. That's probably something that you don't want to do, that's why I was suggesting using an API endpoint or do a GET request if that's good enough. – Radu Diță Feb 17 '17 at 14:41
  • thank you very much. I made it GET and it works well. I used keystore name and attached it to the url as a parameter. I check it in server side to equal the keystore (that I hard coded in server side). I hope it does sth like the csrf_token!! because if the apk is hacked, keystore won't be shown! it is generated at runtime I guess. – Armin Javid Feb 17 '17 at 18:57
  • unfortunately, keystore cannot be used! – Armin Javid Feb 20 '17 at 22:08
0

You can use WWWForm to send in POST and call it from a coroutine:

// this will send it at start
// but you can just call SendToController in another function

string laravel_url = "http://somedomain.com/whatever";

IEnumerator Start () {
    yield return StartCoroutine(SendToController());
}

IEnumerator SendToController()
{
    WWWForm form = new WWWForm();
    form.AddField( "csrf_field", "replace this with what you want!!!!" );
    WWW download = new WWW( laravel_url, form );
    yield return download;
    if(!string.IsNullOrEmpty(download.error)) {
        print( "Error downloading: " + download.error );
    } else {
        // if succesful, do what you want
        Debug.Log(download.text);
    }
}

"Coroutine" is your friend. It will make sending forms a lot easier. You might want to read about it in here: https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html

frost
  • 27
  • 8
  • the problem is that token.. I tried what you said.. again token mismatch error happens! – Armin Javid Feb 16 '17 at 21:52
  • Hi, can I see the code to check what you are sending? – frost Feb 16 '17 at 22:25
  • WWWForm form = new WWWForm (); form.AddField ("device_id", SystemInfo.deviceUniqueIdentifier); form.AddField ("name", name); form.AddField ("score", score); form.AddField ("_token", "adjvajvsfv"); WWW www = new WWW (addScoreURL, form); – Armin Javid Feb 17 '17 at 10:44
  • I also replaced "_token" to "csrf_token" and it did not work. I guess that token must be real and be generated at the time its being sent by laravel. as it works in html forms – Armin Javid Feb 17 '17 at 10:46
  • I've read a little about the laravel and api involved, the csrf token is generated on a server. You need to get that first with another request and replace "adjvajvsfv" with that, I think . – frost Feb 17 '17 at 21:52
  • thank you.. I made a url give me a csrf_token(with GET). and then used that token, but it did'nt work. I think that's because token differs in each request! As I mentionded in @Radu answer, I made the whole thing by GET and added my custom security.. hoping to be secure! – Armin Javid Feb 18 '17 at 06:50