20

I have been following this tutorial to include Google Sign-in support to my Desktop app. The library I'm using is this one.

Everything works and this is the implementation of the authorize() method:

public Credential authorize() throws IOException {
    // Load client secrets.
    InputStream in = GoogleLogin.class.getResourceAsStream("/google/client_secret.json");
    GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

    // Build flow and trigger user authorization request.
    GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
            HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
            .setDataStoreFactory(DATA_STORE_FACTORY)
            .setAccessType("offline")
            .build();

    Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");

    System.out.println("Credentials saved to " + DATA_STORE_DIR.getAbsolutePath());
    return credential;
}

However, from a Credential object, I can only retrieve the access token by calling Credential.getAccessToken(), but what I need is the id token. How can I retrieve the id_token from the user after it's been authenticated?

Morgan
  • 907
  • 1
  • 13
  • 35

1 Answers1

12

I literally figured it out after starting the bounty! It's possible to get the Id Token by inheriting from AuthorizedCodeInstalledApp and providing your own implementation of authorize()

Here's what I did...

public class GoogleAuthCodeInstalledApp extends AuthorizationCodeInstalledApp {

    public GoogleAuthCodeInstalledApp(AuthorizationCodeFlow flow, VerificationCodeReceiver receiver) {
        super(flow, receiver);
    }

    @Override
    public Credential authorize(String userId) throws IOException {
        try {
            Credential credential = getFlow().loadCredential(userId);
            if (credential != null
                    && (credential.getRefreshToken() != null
                    || credential.getExpiresInSeconds() == null
                    || credential.getExpiresInSeconds() > 60)) {
                return credential;
            }
            // open in browser
            String redirectUri = getReceiver().getRedirectUri();
            AuthorizationCodeRequestUrl authorizationUrl
                    = getFlow().newAuthorizationUrl().setRedirectUri(redirectUri);
            onAuthorization(authorizationUrl);
            // receive authorization code and exchange it for an access token
            String code = getReceiver().waitForCode();
            GoogleTokenResponse response = (GoogleTokenResponse) getFlow().newTokenRequest(code).setRedirectUri(redirectUri).execute();
            System.out.println(response.getIdToken()); //YES, THIS IS THE ID TOKEN!!!
            // store credential and return it
            return getFlow().createAndStoreCredential(response, userId);
        } finally {
            getReceiver().stop();
        }
    }

}

After you do that, instead of

Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");

Use:

Credential credential = new GoogleAuthCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");

UPDATE 2018-05-29 - I have found a better, more reliable solution

This solution I found works by adding a CredentialCreatedListener and a CredentialRefreshListener inside our GoogleAuthorizationCodeFlow.Builder.

Here's the sample code:

public Credential authorize() throws IOException {
    InputStream in = GoogleLogin.class.getResourceAsStream("/google/client_secret.json");
    GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
    // Build flow and trigger user authorization request.
    flow = new GoogleAuthorizationCodeFlow.Builder(
            HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
            .setDataStoreFactory(DATA_STORE_FACTORY)
            .setAccessType("offline")
            .setCredentialCreatedListener(new AuthorizationCodeFlow.CredentialCreatedListener() {
                @Override
                public void onCredentialCreated(Credential credential, TokenResponse tokenResponse) throws IOException {
                    DATA_STORE_FACTORY.getDataStore("user").set("id_token", tokenResponse.get("id_token").toString());
                }
            })
            .addRefreshListener(new CredentialRefreshListener() {
                @Override
                public void onTokenResponse(Credential credential, TokenResponse tokenResponse) throws IOException {
                    DATA_STORE_FACTORY.getDataStore("user").set("id_token", tokenResponse.get("id_token").toString());
                }

                @Override
                public void onTokenErrorResponse(Credential credential, TokenErrorResponse tokenErrorResponse) throws IOException {
                    //handle token error response
                }
            })
            .build();

    Credential credential = new AuthorizationCodeInstalledApp(flow, serverReceiver).authorize("user");
    System.out.println("Credentials saved to " + DATA_STORE_DIR.getAbsolutePath());
    return credential;
}

The code is pretty much self-explanatory. Whenever a new Credential is created or refreshed by calling credential.refreshToken(), the listeners will be notified and the id_token will be taken from the TokenResponse (which is actually a GoogleTokenResponse object that contains an id_token field), and we'll use the default DataStoreFactory to save the id_token. The id_token will now be persisted locally, and will be automatically updated by the listeners whenever credential.refreshToken() is called.

Morgan
  • 907
  • 1
  • 13
  • 35
  • Thank you for this--it's exactly what I need. Do I need to be concerned with sharing the client secret within the app though? https://stackoverflow.com/questions/19615372/client-secret-in-oauth-2-0#answer-36830438 indicates I don't, but there aren't any official references to back it up. – CamHart Sep 23 '18 at 08:33
  • Just found https://stackoverflow.com/questions/51399187/oauth2-with-desktop-application-security#answer-51401257 which points out the risks of sharing your client secret for installed desktop apps. https://stackoverflow.com/questions/4419915/how-to-keep-the-oauth-consumer-secret-safe-and-how-to-react-when-its-compromis#answer-4419966 is another great answer too. – CamHart Sep 23 '18 at 09:00
  • calling credential.refreshToken() does not seem to refresh the id_token... maybe it's my implementation though... – CamHart Sep 26 '18 at 08:13
  • Just want to add. Make sure you included "openid" in scopes – Hai nguyen thanh May 18 '20 at 04:01
  • @Morgan what is DATA_STORE_FACTORY kindly tell me i am new so i dont know about it and how i get this value and set it – Adnan haider Oct 30 '21 at 05:44