How to do 3-legged OAuth login with One Tap sign-in on Android

1.1k Views Asked by At

I'm trying to implement 3-legged OAuth login with Google's One Tap sign-in for Android. This is needed to access Gmail from the server side.

The app needs to get a server auth code and pass it to the server which stores it. The server will later exchange it for a refresh token and access token and use it to pull email.

I use the following code (error checking removed for brevity) which gets me a token, but it's NOT a refresh token. What I need is a server auth code that I can exchange for access and refresh tokens like.

// Show the one tap sign in account selector
List<String> scopes = new ArrayList<>(List.of("https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email",
            ..., ... // other scopes come here
));
BeginSignInRequest signInRequest = BeginSignInRequest.builder()
        .setGoogleIdTokenRequestOptions(BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
                .associateLinkedAccounts(LINKED_SERVICE, scopes) // what is this LINKED_SERVICE?
                .setSupported(true)
                .setServerClientId(SERVER_CLIENT_ID)
                .setFilterByAuthorizedAccounts(false)
                .build())
        .setAutoSelectEnabled(false)
        .build();
SignInClient oneTapClient = Identity.getSignInClient(activity);
oneTapClient.beginSignIn(signInRequest).addOnCompleteListener(new OnCompleteListener<BeginSignInResult>() {
    @Override
    public void onComplete(@NonNull Task<BeginSignInResult> task) {
        if (!task.isSuccessful())
            return;
        BeginSignInResult signInResult = task.getResult();
        PendingIntent signInIntent = signInResult.getPendingIntent();
        activity.startIntentSenderForResult(signInIntent.getIntentSender(), REQ_SIGN_IN, null, 0, 0, 0));
    }
});

Then in the intent receiver:

// Handle response from the one tap account selector
public void onResult(Activity activity, int requestCode, int resultCode, Intent data) {
    // Assume requestCode==REQ_SIGN_IN and resultCode is OK (for brevity)
    SignInCredential cred = oneTapClient.getSignInCredentialFromIntent(data);
    String idTokenString = cred.getGoogleIdToken();
    // Now what? This is an Oauth token for client-side use.
    // How to I get a server auth code that can be exchanged for a refresh and access tokens at the server-side?
}

Is there a way to get the server auth code?

Comment: Until now I was using the GoogleSignIn API in the following manner, but it's being phased out and not sure when it's going to stop working (documentation states March 31, 2023).

// Start the OAuth sign in
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
    .requestScopes(new Scope("https://www.googleapis.com/auth/userinfo.profile"), new Scope("https://www.googleapis.com/auth/userinfo.email"))
    .requestServerAuthCode(SERVER_CLIENT_ID)
    .requestEmail()
    .build();
Intent intent = GoogleSignIn.getClient(activity, gso).getSignInIntent();
activity.startActivityForResult(intent, REQ_SIGN_IN);

The result is returned to the activity (again, no error checking)

public void onResult(Activity activity, int requestCode, int resultCode, Intent data) {
    Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
    GoogleSignInAccount acct = task.getResult();
    String authCode = acct.getServerAuthCode(); // Server will exchange this for access and refresh tokens
    // Pass this authCode to the server
}
2

There are 2 best solutions below

13
On

Linked account sign-in uses access and refresh tokens issued by another, non-Google partner site or platform. Data is shared from the partner platform TO Google using OAuth2. In this scenario the partner platform defines the scope names, and Google requests and requests and manages the access and refresh tokens issued by the partner platform -- your app does not manage the credentials. An example of this might be playing music or video managed by a different company on a Google Home hub.

From your comments it sounds like you are looking to request Google scopes and work with Google Account user data? In your example code, you are using authentication scopes only (email, profile) and simply using One Tap to obtain an ID token would be the most straightforward. Should you need to work with Google scopes, for Android you'll currently need to use the older Google Sign-in APIs. Somewhat confusingly, the sign-in options for user authentication are using newer Android API's while authorization to work with Google Account user data remains on the older Google Sign-in APIs.

0
On

New Google Sign-In API using Identity.getSignInClient(activity) doesn't support 3-legged OAuth.

The documentation states:

For new apps we recommend using Google Identity Services instead of the Google Sign-In API for sign-in and sign-up, unless you need authorization, Server-Side Access (!!!), or custom OAuth scopes.

Old Google Sign-in is scheduled to be deprecated at March 31, 2023. However, until now (September 2022) there is no sign that the new API will add support.