1
votes

I want to get authorisation code to enable server-side API access for my app. I do this process in Unity3D with google play games services plugin for Unity. I have function that calls native getToken() function from GoogleAuthUtils class:

 public string GetToken() {
            string token = null;
            Debug.Log("Before RetrieveUserEmail");
            string email = RetrieveUserEmail() ?? "NULL";
            Debug.Log("After RetrieveUserEmail email: " + email);
            string scope = "oauth2:server:client_id:" + "666666666666-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com"
                + ":api_scope:" + "https://www.googleapis.com/auth/plus.login";
            using (AndroidJavaClass jc_unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"),
                jc_gau = new AndroidJavaClass("com.google.android.gms.auth.GoogleAuthUtil")) {
                using(AndroidJavaObject jo_Activity = jc_unityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) {
                    token = jc_gau.CallStatic<string>("getToken", jo_Activity, email, scope);
                }
            }
            Debug.Log("Token " + token);
            return token;
        }

but I get AndroidJavaException: com.google.android.gms.auth.UserRecoverableAuthException: NeedPermission

This function seams to be OK, as it works with

string scope = "audience:server:client_id:" + "666666666666-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com"

and returns audience token.

I don't imagine what am I doing wrong.

Any suggestions?

Or maybe You can clarify, that is using URL call:

https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/plus.login&client_id=666666666666-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com&response_type=code&redirect_uri=http://someurl.com/oauth2callback

redirects me to url like

http://someurl.com/oauth2callback?code=4/YUVerNRxRQ8_XHPJ4USfjhYLCZ-fKoQyD1v5H_cZH_o.IvzKlyDEVOcVrjMoGjtSfTpyjkcImAI&authuser=0&num_sessions=1&session_state=14ac991a51396ecb690abac27e676846c7a8297e..c560&prompt=none

that has in parameter code = 4/YUVer...

is that code the SAME one as that I am trying to get via Unity function?

Thank in advance, I will appreciate any help.

2

2 Answers

0
votes

A few things that could be worth checking:

  • What is your redirect URI?

When you are making a request from Android, your redirect URI will be the device-specific redirect URI, urn:ietf:wg:oauth:2.0:oob.

  • Are you (or your user) canceling from the authorization step?

The error you are getting indicates that the user clicked cancel when authorizing the app (or never clicked confirm). If you're getting an authorization code (the 4/YUY.... string) then this should not be happening.

  • Is the token that you're exchanging the right token?

There are 3 important types of OAuth responses for authZ, access token, refresh token, and authorization code. If you are trying the exchange an access token (or refresh token) as an auth code, the responses will not make any sense. Make sure you're using the authorization code from getToken and are passing the correctly matched redirect URI when performing token exchange.

Going back to your comments, this makes sense:

is that code the SAME one as that I am trying to get via Unity function?

The code that you get after the OAuth server redirect is the code that you want and it is returned from Android in the getToken. For more information on getting / exchanging the code, check out this blog article on Google+ Android Client & Server Sign-In.

For a demo of how to get the code, see the Haiku+ Android client. Relevant code is:

public String getCodeSynchronous() throws GoogleAuthException, CodeException {
    StringBuilder scopeString = new StringBuilder("");
    for (String scope : Constants.SCOPES) {
        scopeString.append(" ").append(scope);
    }
    String scope = new StringBuilder("oauth2:server:client_id:")
            .append(Constants.SERVER_CLIENT_ID)
            .append(":api_scope:")
            .append(scopeString.toString())
            .toString();
    Bundle appActivities = new Bundle();
    String types = TextUtils.join(" ", Constants.ACTIONS);
    appActivities.putString(GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES, types);

    String code = null;
    try {
        code = GoogleAuthUtil.getToken(
                mContext,
                mAccountName,
                scope,
                appActivities);
        // Immediately invalidate so we get a different one if we have to try again.
        GoogleAuthUtil.invalidateToken(mContext, code);
    } catch (IOException e) {
        Log.e(TAG, e.getMessage(), e);
        throw new CodeException("Error: could not establish connection to server.");
    }

    return code;
}

For how to exchange the code server-side, see the Haiku+ Java server:

try {
  // Upgrade the authorization code into an access and refresh token.
  return new GoogleAuthorizationCodeTokenRequest(HaikuPlus.TRANSPORT,
    HaikuPlus.JSON_FACTORY,
    getClientId(),
    getClientSecret(),
    authorization,
    redirectUri).execute();

} catch (TokenResponseException e) {
  //Failed to exchange authorization code.
  logger.log(Level.INFO, "Failed to exchange auth code; return 400", e);
  response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
  return null;
} catch (IOException e) {
  logger.log(Level.INFO, "Failed to exchange auth code; return 400", e);
  response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
  return null;
}
0
votes

I had the same issue. This was how I solved it.

Step 1: I followed Google document at Try Sign-In for Android. So I had Sign-In working. I had to follow this tutorial Start Integrating Google Sign-In into Your Android App to create "Client ID for Android"

Step 2: Similarly, at the same time when I created "Client ID for Android", I had to create "Client ID for Web". This step gave me the "Client ID for Web" which I would use later.

Step 3:

Get token id following this tutorial "Authenticate with a backend server". In method onConnected when sign in, add

GetIdTokenTask getIdTokenTask = new GetIdTokenTask();
getIdTokenTask.execute();

You can find GetIdTokenTask class following the tutorial " Authenticate with a backend server" . Here is an example of onConnected method.

@Override
public void onConnected(Bundle bundle) {
    // onConnected indicates that an account was selected on the device, that the selected
    // account has granted any requested permissions to our app and that we were able to
    // establish a service connection to Google Play services.
    Log.d(TAG, "onConnected:" + bundle);
    mShouldResolve = false;

    if (Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null) {
        Person currentPerson = Plus.PeopleApi.getCurrentPerson(mGoogleApiClient);

        Log.d(TAG, "onConnected:" + "start GetIdTokenTask");

        GetIdTokenTask getIdTokenTask = new GetIdTokenTask();
        getIdTokenTask.execute();
    } else {
        Log.d(TAG, "onConnected:" + "Cannot get current person's information.");
    }
    // Show the signed-in UI
    showSignedInUI();
}

Step 4: Configure the SERVER_CLIENT_ID which we can get from step 2 in GetIdTokenTask class

Here is an example of GetIdTokenTask.

private class GetIdTokenTask extends AsyncTask<Void, Void, String> {

    @Override
    protected String doInBackground(Void... params) {
        String accountName = Plus.AccountApi.getAccountName(mGoogleApiClient);
        Account account = new Account(accountName, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE);
        String scopes = "audience:server:client_id:" + SERVER_CLIENT_ID; // Not the app's client ID.
        try {
            return GoogleAuthUtil.getToken(getApplicationContext(), account, scopes);
        } catch (IOException e) {
            Log.e(TAG, "Error retrieving ID token.", e);
            return null;
        } catch (GoogleAuthException e) {
            Log.e(TAG, "Error retrieving ID token.", e);
            return null;
        }
    }

    @Override
    protected void onPostExecute(String result) {
        Log.i(TAG, "ID token: " + result);
        if (result != null) {
            // Successfully retrieved ID Token
            // ...
        } else {
            // There was some error getting the ID Token
            // ...
        }
    }

}