Search code examples
javascriptunity-game-engineoauth-2.0google-oauthfacebook-unity-sdk

Unity and Google OAuth2 Authorization Code Flow


I am trying to build a Unity application to be deployed with WebGL. I am trying to incorporate Google Sign-In into the application, and so far, this was what I've managed to make work in the Unity WebGL build in Chrome:

  1. User presses on the "Login with Google" button on Unity application, in Tab A.
  2. User is directed to Google Sign In page on another Tab B.
  3. User signs in with Google account, and is redirected to my redirect_uri, which is simply https://localhost, with the auth code parameter.

My question is, is it possible for me to do the following, possible with .jslib files:

  1. Instead of going to redirect_uri on Tab B, instead go back to Tab A without reloading, passing along the auth code.
  2. Building on the line above, have javascript handlers, that:
    1. When auth code is received, initiate request to exchange auth code for the id_token as instructed here.
    2. When id_token is received, call a C# Script function to do further actions with the id_token.

Alternatively, I can set redirect_uri to be an endpoint on my backend server, and perform the auth token -> id_token flow using the Google client SDKs. However, for this approach, I would like to know if i am able to

  1. After the auth token -> id_token flow is completed on the backend server, close the current window, Tab B, and go back to Tab A.
  2. After we’re back on Tab A, redirect Unity to a specific scene (not the login scene anymore, but a home page that users are directed to after they are authenticated).

Would very much appreciate any help i can get :')

EDIT: For better clarity, what I want to achieve is something that FacebookSDK for Unity has done in their FB.LogInWithReadPermissions(). The whole auth code -> access_token flow is seamless, and i get redirected back to the Unity application in Tab A at the end with the access_token.


Solution

  • I managed to find a Javascript solution to achieve my first method. The differences are that because

    1. My application will never be in production
    2. Consistency with my Facebook OAuth implementation,

    I used the implicit flow instead of the authorization code flow, despite it being not the recommended way due to security concerns. However, I think you can easily use the authorization code flow, retrieving the authorization code and passing it on to your backend to exchange for an id token. (as far as I know, you cannot use Javascript/XHR requests to do this exchange)

    So, the flow is that from my C# script, I call a Javascript function from a .jslib file. Basically, the function detects when the OAuth window has redirected back to my redirect_uri, then gets the access_token parameter from the redirected URI, and calls a C# Script function. From there, you should be able to do whatever you need to do (change scene, send to your backend, etc.). Note that there is a try/catch because there will be errors if you attempt to get information from the Google Sign In pages.

    The file is as follows:

    mergeInto(LibraryManager.library, {
      OpenOAuthInExternalTab: function (url, callback) {
        var urlString = Pointer_stringify(url);
        var callbackString = Pointer_stringify(callback);
    
        var child = window.open(urlString, "_blank");
        var interval = setInterval(function() {
            try {
                // When redirected back to redirect_uri
                if (child.location.hostname === location.hostname) {
                    clearInterval(interval) // Stop Interval
                    
                    // // Auth Code Flow -- Not used due to relative complexity
                    // const urlParams = new URLSearchParams(child.location.search);
                    // const authCode = urlParams.get('code');
                    // console.log("Auth Code: " + authCode.toString());
                    // console.log("Callback: " + callbackString);
                    // window.unityInstance.SendMessage('Auth', callbackString, authCode);
    
                    // Implicit Flow
                    var fragmentString = child.location.hash.substr(1);
                    var fragment = {};
                    var fragmentItemStrings = fragmentString.split('&');
                    for (var i in fragmentItemStrings) {
                        var fragmentItem = fragmentItemStrings[i].split('=');
                        if (fragmentItem.length !== 2) {
                            continue;
                        }
                        fragment[fragmentItem[0]] = fragmentItem[1];
                    }
                    var accessToken = fragment['access_token'] || '';
                    console.log("access_token: " + accessToken);
                    child.close();
    
                    // Invoke callback function
                    window.unityInstance.SendMessage('Auth', callbackString, accessToken);l
                }
            }
            catch(e) {
                // Child window in another domain
                console.log("Still logging in ...");
            }
        }, 50);
      }
    });
    

    Then, in my C# script, I call this function using the following:

    public class GoogleHelper : MonoBehaviour
    {   
        [DllImport("__Internal")]
        private static extern void OpenOAuthInExternalTab(string url, string callbackFunctionName);
        // ...
    
        public void Login(string callbackFunctionName) {
            var redirectUri = "https://localhost";
            var url = "https://accounts.google.com/o/oauth2/v2/auth"
                    + $"?client_id={clientId}"
                    + "&response_type=token"
                    + "&scope=openid%20email%20profile"
                    + $"&redirect_uri={redirectUri}";
            OpenOAuthInExternalTab(url, callbackFunctionName);
        }
        // ...
    }
    
    
    

    Of course, this is super hacky, and I'm not very familiar with Javascript and so don't really know the implication of the code above, but it works for my use case.