How to get access token after user is signed in from Gmail in Android?
Asked Answered
S

7

44

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.

Sisto answered 30/11, 2015 at 12:8 Comment(3)
have u tried GoogleSignInAccount object.getServerAuthCode() ?Marelya
Looks as if getServerAuthCode() gets you a one time Auth Code which you upload to your server. The your server requests AccessToken using the auth code.Duval
You want to logged in with G+ and after login you you want Google auth tockenSepta
W
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"
         }
Warlock answered 9/1, 2016 at 7:34 Comment(26)
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.Duval
@scottyab: in the link at the bottom, I think from step#3, access token gotWarlock
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. ThxDuval
@Duval do you mean that in your current project, accessToken got directly in client-side?Warlock
@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 tokenWarlock
yep, that's what I'm after. The 100 rep bounty is for a working code sample. Using details from GoogleSignInOptions and returned GoogleSignInAccount. ThanksDuval
@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 :)Warlock
.add("code", "4/4-GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8") is the auth codeWarlock
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?Embolism
@Warlock where would I post this if I am using Amazon AWS? I can't find any conclusive answers.Shrine
@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 :)Warlock
@Embolism IMO, to directly get the access token without using the auth code, you can also try with GoogleAuthUtil.getToken at developers.google.com/android/guides/…, however, about security, please read this blog spotWarlock
@Warlock I'm following your steps but I'm getting this error { "error": "invalid_client", "error_description": "Unauthorized" } Any idea?Stygian
@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?Warlock
@Warlock 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?Stygian
@RobertoFrontado please share your Postman screenshot and Android code so that I can check tomorrowWarlock
@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)!Dhyana
@Warlock 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?Rounders
@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 codeWarlock
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???Dreda
Is there a way to do this without the secret?Mycobacterium
Thanks. A perfect way to get access token.Kristy
I know this works cause I'm using it to log it, but how about log out?Featherstone
@Featherstone I think you can refer developers.google.com/identity/sign-in/android/disconnectWarlock
Great Help. I have get a calendar scope by using this very easilyNovice
GoogleApiClient is deprecatedWaterline
D
8

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())}
            }
        }
    })
}
Detector answered 20/2, 2020 at 19:2 Comment(1)
How to get client_secret ? we have file of client_secret, no string available for it.Lemuel
D
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!

Dhyana answered 16/9, 2016 at 18:50 Comment(6)
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.Warlock
Ah, your response does not have "refresh_token". Did you use the same API endpoint as mine? That is 'googleapis.com/oauth2/v4/token'Warlock
Yes I did use the same endpoint. Hmm, wonder why I am not getting the refresh_token. Did not notice that earlier :|Dhyana
Uhm, some more links will have different cases, pls read developers.google.com/identity/protocols/OpenIDConnect and developers.google.com/identity/protocols/OAuth2ForDevices and developers.google.com/identity/protocols/OAuth2InstalledApp. Next week, I will review my codeWarlock
Thank you! Do let me know if you find something!Dhyana
seriously? you have to use your client_secret in your code?Egan
C
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

Cartelize answered 7/1, 2019 at 20:4 Comment(0)
B
5

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'
  }
Bullpen answered 12/5, 2021 at 8:44 Comment(1)
Superb, works like a charm. No bulky code to write. </br> why google can't amend it in official docs!!Geochronology
T
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);
Threadbare answered 11/1, 2018 at 20:5 Comment(1)
@Suresh code is ServerAuthCode: signInOptions.requestServerAuthCode(getString(R.string.default_web_client_id)); .... onSuccess{ GoogleSignInAccount.getServerAuthCode(); <<<Cartelize
C
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. 👍

Cockleboat answered 26/12, 2018 at 5:50 Comment(3)
GoogleAuthUtil.getToken - is deprecated long time ago. And will stop working eventually.Cartelize
@OleksandrAlbul The official document said that it isn't a deprecated API. developers.google.com/android/reference/com/google/android/gms/…Cockleboat
@Cockleboat Now it does.Twinkling

© 2022 - 2024 — McMap. All rights reserved.