private TokensAndUrlAuthData refreshToken() throws IOException {
    monitor.info(() -> "Refreshing OAuth token");
    Request request =
        new Request.Builder()
            .url(authData.getTokenServerEncodedUrl())
            .addHeader("Accept", JSON.toString())
            .post(
                new FormBody.Builder()
                    .add("grant_type", "refresh_token")
                    .add("client_id", appCredentials.getKey())
                    .add("client_secret", appCredentials.getSecret())
                    .add("refresh_token", authData.getRefreshToken())
                    .build())
            .build();

    try (Response response = client.newCall(request).execute()) {
      if (response.code() >= 400) {
        throw new IOException(
            format(
                "Error while refreshing token (%d): %s",
                response.code(), new String(response.body().bytes(), StandardCharsets.UTF_8)));
      }
      byte[] body = response.body().bytes();
      RefreshTokenResponse responsePayload;
      try {
        responsePayload = om.readValue(body, RefreshTokenResponse.class);
      } catch (JsonParseException | JsonMappingException e) {
        throw new IOException(
            format(
                "Unexpected response while refreshing token: %s",
                new String(body, StandardCharsets.UTF_8)),
            e);
      }
      return new TokensAndUrlAuthData(
          responsePayload.getAccessToken(),
          responsePayload.getRefreshToken().orElse(authData.getRefreshToken()),
          authData.getTokenServerEncodedUrl());
    }
  }
@param initialAuthData The auth data to be used for the first request
@param appCredentials containing the OAuth client ID and client secret
@param client to use for making HTTP requests when refreshing the token
@param monitor for logging
extensions/data-transfer/portability-data-transfer-generic/src/main/java/org/datatransferproject/datatransfer/generic/auth/OAuthTokenManager.java