javaspring-bootauthenticationgoogle-drive-apidrive

Use logged user's idToken to invoke Google Drive API


I would like to list the logged user's files on Google Drive with my Spring Boot java application.

I have implemented the Google login flow, which works fine. I ask the user at login to permit access to his/her Google Drive. In a controller method, I access the idToken of the user. The intention is that I reuse this token and pass it to a Google Drive API call, but the API rejects this token. The API returns with the below error:

{
  ...

  "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
  
  ...
}

It looks like I cannot reuse this idToken. I guess, I will need another type of token, but cannot figure out, what kind of, and how. Can anyone advise?

Below is the controller method I used for testing:

    @GetMapping("")
    public ResponseEntity<List<String>> listFiles(Authentication authentication) throws IOException,
                                                                                        GeneralSecurityException {
        // get idToken of authenticated user
        OAuth2AuthenticationToken oauth2Auth = (OAuth2AuthenticationToken) authentication;
        String idToken = ((DefaultOidcUser) oauth2Auth.getPrincipal()).getIdToken().getTokenValue();
        Credentials credentials = IdTokenCredentials.create(AccessToken.newBuilder().setTokenValue(idToken).build());

        // Build the Drive service and list files.
        Drive service = new Drive.Builder(GoogleNetHttpTransport.newTrustedTransport(),
                                          new GsonFactory(),
                                          new HttpCredentialsAdapter(credentials)).setApplicationName(APPLICATION_NAME)
                                                                                  .build();
        FileList result = service.files().list().setPageSize(10).setFields("files(name)").execute();
        List<String> fileNames = result.getFiles().stream().map(file -> file.getName()).toList();
        return ResponseEntity.ok(fileNames);
    }


Solution

  • You need to use Spring's OAuth2AuthorizedClientService to exchange the ID token for an access token.

    private final OAuth2AuthorizedClientService clientService;
    
    @GetMapping("")
    public ResponseEntity<List<String>> listFiles(Authentication authentication) throws IOException,
                                                                                        GeneralSecurityException {
        // get idToken of authenticated user
        OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
    
        OAuth2AuthorizedClient client =
            clientService.loadAuthorizedClient(
                oauthToken.getAuthorizedClientRegistrationId(),
                oauthToken.getName());
    
        String accessToken = client.getAccessToken().getTokenValue();
    
        Credentials credentials = IdTokenCredentials.create(AccessToken.newBuilder().setTokenValue(accessToken).build());
    
        // Build the Drive service and list files.
        Drive service = new Drive.Builder(GoogleNetHttpTransport.newTrustedTransport(),
                                          new GsonFactory(),
                                          new HttpCredentialsAdapter(credentials)).setApplicationName(APPLICATION_NAME)
                                                                                  .build();
        FileList result = service.files().list().setPageSize(10).setFields("files(name)").execute();
        List<String> fileNames = result.getFiles().stream().map(file -> file.getName()).toList();
        return ResponseEntity.ok(fileNames);
    }