42

I am following Google Sign in for Android. Now I can get the idToken but my back end server that I have used earlier is expecting access Token as I was using Google+ Login earlier. Now I don't want to alter my server side. But still how can I use Google Sign in and get the access Token in my android app so that I can validate my user to my back end server.

I was using GooglePlay Service 7.5.0 previously and now I am using GooglePlay Service latest 8.3.0.

BNK
  • 23,994
  • 8
  • 77
  • 87
aman.nepid
  • 2,864
  • 8
  • 40
  • 48

7 Answers7

81

For your requirements, you can use the following code:

Firstly, make sure you have a valid Web OAuth 2.0 Client ID:

<!-- Server Client ID.  This should be a valid Web OAuth 2.0 Client ID obtained
         from https://console.developers.google.com/ -->
    <string name="server_client_id">...e4p8.apps.googleusercontent.com</string>

Then inside Activity class:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ...

    // For sample only: make sure there is a valid server client ID.
    validateServerClientID();

    // [START configure_signin]
    // Configure sign-in to request offline access to the user's ID, basic
    // profile, and Google Drive. The first time you request a code you will
    // be able to exchange it for an access token and refresh token, which
    // you should store. In subsequent calls, the code will only result in
    // an access token. By asking for profile access (through
    // DEFAULT_SIGN_IN) you will also get an ID Token as a result of the
    // code exchange.
    String serverClientId = getString(R.string.server_client_id);
    GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestScopes(new Scope(Scopes.DRIVE_APPFOLDER))
            .requestServerAuthCode(serverClientId)
            .requestEmail()
            .build();
    // [END configure_signin]

    // Build GoogleAPIClient with the Google Sign-In API and the above options.
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
            .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
            .build();
}

private void getAuthCode() {
    // Start the retrieval process for a server auth code.  If requested, ask for a refresh
    // token.  Otherwise, only get an access token if a refresh token has been previously
    // retrieved.  Getting a new access token for an existing grant does not require
    // user consent.
    Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
    startActivityForResult(signInIntent, RC_GET_AUTH_CODE);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == RC_GET_AUTH_CODE) {
        GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
        Log.d(TAG, "onActivityResult:GET_AUTH_CODE:success:" + result.getStatus().isSuccess());

        if (result.isSuccess()) {
            // [START get_auth_code]
            GoogleSignInAccount acct = result.getSignInAccount();
            String authCode = acct.getServerAuthCode();

            // Show signed-in UI.
            mAuthCodeTextView.setText(getString(R.string.auth_code_fmt, authCode));
            updateUI(true);

            // TODO(user): send code to server and exchange for access/refresh/ID tokens.
            // [END get_auth_code]
        } else {
            // Show signed-out UI.
            updateUI(false);
        }
    }
}

You can see the entire code at the following ServerAuthCodeActivity.java

The result, if you use that sample, looks like the following screenshot:

BNK's screenshot

Then, you can follow the steps mentioned at the Google's documentation below (from step #3. Send the auth code to your app's backend using HTTPS POST):

Google Sign-In for Android - Enabling Server-Side Access


UPDATE: from the comments, if you want to get access token directly from android client app, please use the following sample code (replaced with your client_id, client_secret and the auth code)

OkHttpClient client = new OkHttpClient();
    RequestBody requestBody = new FormEncodingBuilder()
            .add("grant_type", "authorization_code")
            .add("client_id", "812741506391-h38jh0j4fv0ce1krdkiq0hfvt6n5amrf.apps.googleusercontent.com")
            .add("client_secret", "{clientSecret}")
            .add("redirect_uri","")
            .add("code", "4/4-GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8")
            .build();
    final Request request = new Request.Builder()
            .url("https://www.googleapis.com/oauth2/v4/token")
            .post(requestBody)
            .build();
    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(final Request request, final IOException e) {
            Log.e(LOG_TAG, e.toString());                
        }

        @Override
        public void onResponse(Response response) throws IOException {
            try {
                JSONObject jsonObject = new JSONObject(response.body().string());
                final String message = jsonObject.toString(5);
                Log.i(LOG_TAG, message);                    
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    });

Please use compile 'com.squareup.okhttp:okhttp:2.6.0' (ver 3-RC1 will have different classes)

With a sucessful response, you will have the following info in logcat:

I/onResponse: {
              "expires_in": 3600,
              "token_type": "Bearer",
              "refresh_token": "1\/xz1eb0XU3....nxoALEVQ",
              "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjQxMWY1Ym......yWVsUA",
              "access_token": "ya29.bQKKYah-........_tkt980_qAGIo9yeWEG4"
         }
BNK
  • 23,994
  • 8
  • 77
  • 87
  • Thanks for your effort answering the question. However you've shown how to get the Auth Code, not the accessToken. I'm looking for Android based code to get the accessToken. – scottyab Jan 11 '16 at 08:36
  • @scottyab: in the link at the bottom, I think from step#3, access token got – BNK Jan 11 '16 at 08:47
  • Step 3 is "Send the auth code to your app's backend using HTTPS POST" not get the accessToken? (unless i'm missing something). I understand the prefered way is for the server to make the request with the auth code to get the accessToken. However it means our serverside API would need to be updated to handle accessToken or Auth code. Raised the bounty to find a way of getting to accessToken to avoid server side code changes. Thx – scottyab Jan 11 '16 at 08:56
  • @scottyab do you mean that in your current project, accessToken got directly in client-side? – BNK Jan 11 '16 at 08:59
  • @scottyab: if so, I think from client app (android), you send a POST request to `https://www.googleapis.com/oauth2/v4/token` with that auth code (and `client_id`, `client_secret`, `grant_type`) to get the access token – BNK Jan 11 '16 at 09:09
  • 2
    yep, that's what I'm after. The 100 rep bounty is for a working code sample. Using details from `GoogleSignInOptions` and returned `GoogleSignInAccount`. Thanks – scottyab Jan 11 '16 at 09:13
  • @scottyab: please read my upaded answer, with that code (sample POST body), I will get the response from Google server `{ "error_description": "The OAuth client was deleted.", "error": "deleted_client" }` of course :) – BNK Jan 11 '16 at 09:24
  • `.add("code", "4/4-GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8")` is the auth code – BNK Jan 11 '16 at 09:26
  • 2
    Excellent answer. couldn't find anywhere solution for this problem even on the official docs. wondering why android has not exposed a public api directly for getting the access token? – thedarkpassenger Mar 11 '16 at 17:06
  • @BNK where would I post this if I am using Amazon AWS? I can't find any conclusive answers. – Jaidyn Belbin Jun 27 '16 at 08:39
  • @JaidynBelbin sorry, I haven't ever tried with Amazon AWS, so I suggest that if you have any problem with it, please create a new SO question so that SOers can help you :) – BNK Jun 27 '16 at 08:42
  • @thedarkpassenger IMO, to directly get the access token without using the auth code, you can also try with `GoogleAuthUtil.getToken` at https://developers.google.com/android/guides/http-auth#specifying_scopes, however, about security, please read [this blog spot](http://android-developers.blogspot.com/2016/05/improving-security-and-user-experience.html) – BNK Jun 30 '16 at 01:46
  • @BNK I'm following your steps but I'm getting this error `{ "error": "invalid_client", "error_description": "Unauthorized" }` Any idea? – Roberto Frontado Jul 13 '16 at 21:34
  • @RobertoFrontado do you mean your app got that error message at the step getting the access token? Make sure all the values of `grant_type`, `client_id`, `client_secret`, `code` are valid. Have you tried these values with some REST tools such as PostMan yet? – BNK Jul 13 '16 at 22:20
  • @BNK Yeah, I using Postman. I believe I'm using the right credentials, `"grant_type" = "authorization_code"`, `"client_id" = "web_client_id"`, `"client_secret" = "secret_key"` and `"code" = "4/IX-H2xr8wF2Z7aKhkxQlcz87GrSaDbzVTjQo6jRZt6I"`, can you tell with that info if I'm doing something wrong? – Roberto Frontado Jul 14 '16 at 08:38
  • @RobertoFrontado please share your Postman screenshot and Android code so that I can check tomorrow – BNK Jul 14 '16 at 10:00
  • 2
    @RobertoFrontado I was having similar problems with the request. The request was missing the "id_token" parameter. Once I added that the request was successful. Check out my answer for details below(just added one line to this answer)! – Narayan Acharya Sep 16 '16 at 18:53
  • @BNK I am getting this error { "error": "unsupported_grant_type", "error_description": "Invalid grant_type: " } when I tried this. Any idea , whether google has changed anything about this api? – davidvarghese Feb 06 '17 at 08:16
  • @user3665376 sorry, I have no testing environment to check these days, however, I don't think they have changed. IMO you can search more about that error description in SO or review your code – BNK Feb 08 '17 at 04:16
  • 1
    I have made the same request - `grant_type`, `client_id`, `client_secret`, `code` and `id_token` , But i am still getting the error - `{"error":"invalid_client","error_description":"Unauthorized"}` . The client id and secret are of web client. Any ideas??? – iMDroid Sep 12 '17 at 07:38
  • Is there a way to do this without the secret? – stefana Nov 22 '17 at 14:42
  • 1
    Thanks. A perfect way to get access token. – Kunwar Shekhar Singh May 16 '19 at 06:09
  • I know this works cause I'm using it to log it, but how about log out? – pamobo0609 May 23 '19 at 18:37
  • @pamobo0609 I think you can refer https://developers.google.com/identity/sign-in/android/disconnect – BNK May 28 '19 at 23:22
  • 1
    Great Help. I have get a calendar scope by using this very easily – Deepak Gupta Jun 10 '20 at 14:32
  • 1
    GoogleApiClient is deprecated – laim2003 Dec 20 '20 at 20:42
7

BNK has it spot on for the most part. The Activity class is the same as BNKs answer only with adding the OkHttp part once you get the GoogleSignInAccount in the onActivityResult() method.

But I was still getting errors with the OkHttp request part. Finally after a bit of testing(and part luck) around in Postman, I found that I was missing the id_token parameter. The OkHttp request was missing one parameter i.e the id_token. Use the ID token that you get from the GoogleSignInAccount something like this

GoogleSignInAccount acct = result.getSignInAccount();
String idTokenString = acct.getIdToken();

Now use this idTokenString along with all the parameters in the OkHttp part of BNK's answer somewhat like this

...

RequestBody requestBody = new FormEncodingBuilder()
            .add("grant_type", "authorization_code")
            .add("client_id", "alpha-numeric-string-here.apps.googleusercontent.com")
            .add("client_secret", "{clientSecret}")
            .add("redirect_uri","")
            .add("code", "4/4-alphabetic-string-here")
            .add("id_token", idTokenString) // Added this extra parameter here
            .build();

...

The response one gets is same as BNKs answer

{
  "access_token": "ya29.CjBgA_I58IabCJ...remainingAccessTokenHere",
  "token_type": "Bearer",
  "expires_in": 3577,
  "id_token": "eyJhbGciOiJS...veryLongStringHere"
}

Now send this access_token to your backend server to authenticate just like you used to do during the times of GoogleAuthUtil and PlusAPI.

Hope this helps :) Special thanks to BNK!

Narayan Acharya
  • 1,459
  • 1
  • 18
  • 33
  • Thank you :), I will check the document when I have free time. Perhaps now there are some changes since the time I posted my answer. – BNK Sep 17 '16 at 00:18
  • Ah, your response does not have "refresh_token". Did you use the same API endpoint as mine? That is 'https://www.googleapis.com/oauth2/v4/token' – BNK Sep 17 '16 at 00:21
  • Yes I did use the same endpoint. Hmm, wonder why I am not getting the refresh_token. Did not notice that earlier :| – Narayan Acharya Sep 17 '16 at 06:47
  • Uhm, some more links will have different cases, pls read https://developers.google.com/identity/protocols/OpenIDConnect and https://developers.google.com/identity/protocols/OAuth2ForDevices and https://developers.google.com/identity/protocols/OAuth2InstalledApp. Next week, I will review my code – BNK Sep 17 '16 at 23:23
  • Thank you! Do let me know if you find something! – Narayan Acharya Sep 18 '16 at 06:28
  • seriously? you have to use your client_secret in your code? – Emil Jun 01 '18 at 15:14
7

Here it`s my approach with Kotlin, (this is my first Answer on StackOverflow, if there is something wrong, missing, or that i can do it better, let me know)

On the Login Actvity

private fun configureGoogleSignIn() {
    mGoogleSignInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(getString(R.string.default_web_client_id))
        .requestServerAuthCode(getString(R.string.server_client_id_oauth))
        .requestEmail()
        .build()
    mGoogleSignInClient = GoogleSignIn.getClient(this, mGoogleSignInOptions)
}

private fun signInWithGoogle() {
    val signInIntent: Intent = mGoogleSignInClient.signInIntent
    startActivityForResult(signInIntent, RC_SIGN_IN)
}

Make sure to Call configureGoogleSignIn() function on the OnCreate

Then to get the result

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    callbackManager?.onActivityResult(requestCode, resultCode, data)


    if (requestCode == RC_SIGN_IN) {
        val tag = "onActivityResult RC_SIGN_IN"
        val task: Task<GoogleSignInAccount> = GoogleSignIn.getSignedInAccountFromIntent(data)
        try {
            val account = task.getResult(ApiException::class.java)
            firebaseAuthWithGoogle(account!!)
            getIdTokenFromFirebaseAuth()

            var acct = GoogleSignIn.getLastSignedInAccount(this)
            if (acct != null) {
                var personName = acct.displayName
                firstName = acct.givenName!!
                lastName = acct.familyName!!
                userEmail = acct.email!!
                authCode = acct.serverAuthCode!! //THIS is what you looking for
                googleIdToken2 = acct.idToken!!
                Log.d(tag, authCode)
                Log.d(tag, googleIdToken2)
                var personId = acct.id
                //todo pegar foto do google e por no cadastro do usuario
                var personPhoto = acct.photoUrl
                spinner.visibility = View.GONE
                getGoogleAccessToken()
            }
        } catch (e: ApiException) {
            spinner.visibility = View.GONE
            infoToUserTextView.text = getString(R.string.ops_we_had_a_problem)
        }
    }
}

Then make a Call To Google API (i`m using Retrofit), using this interface make :

@FormUrlEncoded
@POST
fun getAccessTokenGoogle(
    @Url url: String,
    @Field("grant_type") grant_type: String,
    @Field("client_id") client_id: String,
    @Field("client_secret") client_secret: String,
    @Field("redirect_uri") redirect_uri: String,
    @Field("code") authCode: String,
    @Field("id_token") id_token: String
):Call<GoogleSignInAccessTokenDataClass>

Here it`s the GoogleSignInAccessTokenDataClass

data class GoogleSignInAccessTokenDataClass(
val access_token: String,
val expires_in: Int,
val id_token: String,
val token_type: String

)

Make the Call on the Login Activity

private fun getGoogleAccessToken(){
    val call = RetrofitGet().userInfoGson().getAccessTokenGoogle(
        grant_type = "authorization_code", client_id = getString(R.string.server_client_id_oauth),
        client_secret = getString(R.string.server_client_secret_oauth), redirect_uri = "",
        authCode = authCode, id_token =googleIdToken2, url = googleTokenUrl
    )

    call.enqueue(object : Callback<GoogleSignInAccessTokenDataClass>{
        val tag = "getGoogleAccessToken"
        override fun onFailure(call: Call<GoogleSignInAccessTokenDataClass>, t: Throwable) {
            Log.e(tag, t.toString())
        }

        override fun onResponse(
            call: Call<GoogleSignInAccessTokenDataClass>,
            response: Response<GoogleSignInAccessTokenDataClass>
        ) {
            if (response.isSuccessful){
                val responseBody = response.body()
                googleAccessToken = responseBody!!.access_token
                Log.d(tag, googleAccessToken)
            }else{
                try {
                    val responseError = response.errorBody()!!.string()
                    Log.e(tag, responseError)
                }catch (e:Exception){Log.e(tag, e.toString())}
            }
        }
    })
}
Júlio Reis
  • 306
  • 5
  • 7
5

Thanks to @BNK, he has provided the working solution. And here is an official guide how to get 'access token' from 'auth code': https://developers.google.com/identity/protocols/OAuth2WebServer#exchange-authorization-code

Here I want to provide my solution with pure Android SDK classes. In case you do not want to add fancy library just for this purpose:

private String mAccessToken;
private long mTokenExpired;

private String requestAccessToken(GoogleSignInAccount googleAccount) {
    if (mAccessToken != null && SystemClock.elapsedRealtime() < mTokenExpired) return mAccessToken;
    mTokenExpired = 0;
    mAccessToken = null;

    HttpURLConnection conn = null;
    OutputStream os = null;
    InputStream is = null;
    InputStreamReader isr = null;
    BufferedReader br = null;

    try {
        final URL url = new URL("https://www.googleapis.com/oauth2/v4/token");
        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setUseCaches(false);
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setConnectTimeout(3000);
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

        final StringBuilder b = new StringBuilder();
        b.append("code=").append(googleAccount.getServerAuthCode()).append('&')
         .append("client_id=").append(getString(R.string.default_web_client_id)).append('&')
         .append("client_secret=").append(getString(R.string.client_secret)).append('&')
         .append("redirect_uri=").append("").append('&')
         .append("grant_type=").append("authorization_code");

        final byte[] postData = b.toString().getBytes("UTF-8");

        os = conn.getOutputStream();
        os.write(postData);

        final int responseCode = conn.getResponseCode();
        if (200 <= responseCode && responseCode <= 299) {
            is = conn.getInputStream();
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);
        } else {
            Log.d("Error:", conn.getResponseMessage());
            return null;
        }

        b.setLength(0);
        String output;
        while ((output = br.readLine()) != null) {
            b.append(output);
        }

        final JSONObject jsonResponse = new JSONObject(b.toString());
        mAccessToken = jsonResponse.getString("access_token");
        mTokenExpired = SystemClock.elapsedRealtime() + jsonResponse.getLong("expires_in") * 1000;
        return mAccessToken;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
            }
        }
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
            }
        }
        if (isr != null) {
            try {
                isr.close();
            } catch (IOException e) {
            }
        }
        if (br != null) {
            try {
                br.close();
            } catch (IOException e) {
            }
        }
        if (conn != null) {
            conn.disconnect();
        }
    }
    return null;
}

Run this method on background thread. Also client_id and client_secret you need to get from Google APIs console.

Google APIs console id and secret

Oleksandr Albul
  • 1,611
  • 1
  • 23
  • 31
3

This is the simplest approach to get accessToken in Android

 val httpTransport = AndroidHttp.newCompatibleTransport()
val jsonFactory: JsonFactory = JacksonFactory.getDefaultInstance()

tokenResponse = GoogleAuthorizationCodeTokenRequest(
                httpTransport,
                jsonFactory,
                "https://www.googleapis.com/oauth2/v4/token",
                clientId,
                clientSecret,
                account.serverAuthCode,
                "" //optional param (redirect url)
            ).execute()

run it on background thread

Android use these libraries

    implementation 'com.google.android.gms:play-services-auth:19.0.0'

  implementation('com.google.api-client:google-api-client-android:1.23.0') {
    exclude group: 'org.apache.httpcomponents'
  }
Nitin Prakash
  • 927
  • 9
  • 16
2

In case anyone else is having issues making the final request to grab the access token from google. below is a tested and working approach as of 11-01-2018. Using retrofit2.

First of all, Here's link to google doc about the token exchange endpoint : https://developers.google.com/identity/protocols/OAuth2WebServer#exchange-authorization-code

public interface GoogleService {

@POST("token")
@FormUrlEncoded
@Headers("Content-Type:application/x-www-form-urlencoded")
Call<GoogleAuthData> getToken(
        @Field("grant_type") String grantType,
        @Field("client_id") String clientId,
        @Field("client_secret") String clientSecret,
        @Field("redirect_uri") String redirectUri,
        @Field("code") String code);
}

Then call it like this :

Call<GoogleAuthData> call = RetroClient.getGoogleService().getToken(
            "authorization_code", context.getString(R.string.server_client_id),
            context.getString(R.string.server_client_secret), "", authCode);
  • @Suresh code is ServerAuthCode: signInOptions.requestServerAuthCode(getString(R.string.default_web_client_id)); .... onSuccess{ GoogleSignInAccount.getServerAuthCode(); << – Oleksandr Albul Jan 07 '19 at 14:52
1

I found a way to get access token without idToken, code, secret or any requests(like post to "https://www.googleapis.com/oauth2/v4/token"). All you need is only "client id". Follow this steps:

  1. Use "GoogleSignIn" to get sign in and get the "Account" object.

    GoogleSignIn.getClient(
            ctx,
            GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                    .requestEmail()
                    .requestProfile()
                    .requestIdToken(KEY.GOOGLE_CLIENT_ID)
                    .requestServerAuthCode(KEY.GOOGLE_CLIENT_ID, true)
                    .build())
            .let { client ->
                client.signOut()
                    .let { task ->
                        Observable.create<GoogleSignInClient> { ob ->
                            task.addOnCompleteListener { ob.onNext(client) }
                        }
                    }
            }
            .flatMap {
                ctx.startActivityForResult(it.signInIntent, RC_SIGN_IN)
                ctx.activityResultObservable
            }
            .filter { it.requestCode == RC_SIGN_IN }
            .map {
                GoogleSignIn
                        .getSignedInAccountFromIntent(it.data)
                        .getResult(ApiException::class.java)
            }
    

Here I'm using RxJava to write the code, you can write your code without it.

  1. Within the "Account" object, you can get the access token by using "GoogleAuthUtil".

            .flatMap { result ->
                Observable.create<AuthData> {
                    val scope = "oauth2:https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.profile"
                    val accessToken = GoogleAuthUtil.getToken(context, result.account, scope)
                    // now you can use this token
                    it.onNext(accessToken)
                }
            }
    

The function "GoogleAuthUtil::getToken" makes a request, so you cannot run it in UI thread. Now you can send this token to your server.

yuriel
  • 91
  • 1
  • 7
  • 2
    GoogleAuthUtil.getToken - is deprecated long time ago. And will stop working eventually. – Oleksandr Albul Jan 07 '19 at 20:05
  • @OleksandrAlbul The official document said that it isn't a deprecated API. https://developers.google.com/android/reference/com/google/android/gms/auth/GoogleAuthUtil.html#getToken(android.content.Context,%20java.lang.String,%20java.lang.String,%20android.os.Bundle) – yuriel Jan 18 '19 at 05:19
  • 1
    @yuriel Now it does. – PhilBa Aug 01 '19 at 19:58