Repository: firebase/firebase-admin-java
Pull request #71 (2017-09-12 07:57:28)

Migrating to GoogleCredentials
### Migrating to `GoogleCredentials`
1. Deprecated the `FirebaseCredential` and `FirebaseCredentials` interfaces.
2. Added `FirebaseOptions.Builder.setCredentials(GoogleCredentials)` method, which is the new recommended way to pass credentials to the SDK.
3. Using `GoogleCredentials` directly in `FirebaseAuth`, `CloudStorageClient` and other places that make authenticated HTTP calls.
4. Removed old logic for token caching, and token change notifications. All this is now handled by `GoogleCredentials`.

### Project ID Lookup
1. Added `FirebaseOptions.Builder.setProjectId(String)` method, which can be used to initialize the SDK with an explicitly specified project ID.
2. 3-level (options, credentials and environment) project ID lookup mechanism in `ImplOnlyFirebaseTrampolines`

hiranya911: Resolves #57
hiranya911: Made the suggested changes. I did a long running test several days ago, which worked out nicely. I started another just to be sure. Will update this issue with the results.
hiranya911: 
[firebase.txt](https://github.com/firebase/firebase-admin-java/files/1307202/firebase.txt)
I just completed a long running test (debug log attached). Results summary:

* Test duration: ~17 hours
* Unexpected connection losses: 0
* Periodic refresh events:  18 (in near perfect 55 minute intervals)

Disconnect events (The initial `false` status is from when the app is starting up):
```
grep "CONNECTION STATUS" -B 1 firebase.txt 
2017-09-14 17:54:57 [FirebaseDatabaseWorker] DEBUG com.google.firebase.database.core.view.EventRaiser - Raising 1 event(s)
CONNECTION STATUS: false
--
2017-09-14 17:54:57 [FirebaseDatabaseEventTarget] DEBUG com.google.firebase.database.core.view.EventRaiser - Raising /.info/connected: VALUE: true
CONNECTION STATUS: true
```

Refresh events:
```
grep "Refreshing OAuth2 credential" firebase.txt 
2017-09-14 18:49:57 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-14 19:44:57 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-14 20:39:57 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-14 21:34:57 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-14 22:29:57 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-14 23:24:57 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 00:19:58 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 01:14:58 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 02:09:58 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 03:04:58 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 03:59:58 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 04:54:58 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 05:49:58 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 06:44:59 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 07:39:59 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 08:34:59 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 09:29:59 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
2017-09-15 10:24:59 [pool-4-thread-1] DEBUG com.google.firebase.FirebaseApp - Refreshing OAuth2 credential
```

Patch:
diff --git a/pom.xml b/pom.xml
index fcf967f..6596cd2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -312,6 +312,7 @@
     </build>
 
     <dependencies>
+        <!-- Google Cloud Platform dependencies -->
         <dependency>
             <groupId>com.google.api-client</groupId>
             <artifactId>google-api-client</artifactId>
@@ -333,31 +334,40 @@
             <artifactId>google-http-client</artifactId>
             <version>1.22.0</version>
         </dependency>
-        <dependency>
-            <groupId>org.json</groupId>
-            <artifactId>json</artifactId>
-            <version>20160810</version>
-        </dependency>
-        <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava</artifactId>
-            <version>20.0</version>
-        </dependency>
         <dependency>
             <groupId>com.google.api</groupId>
             <artifactId>api-common</artifactId>
             <version>1.1.0</version>
         </dependency>
+        <dependency>
+            <groupId>com.google.auth</groupId>
+            <artifactId>google-auth-library-oauth2-http</artifactId>
+            <version>0.8.0</version>
+        </dependency>
         <dependency>
             <groupId>com.google.cloud</groupId>
             <artifactId>google-cloud-storage</artifactId>
             <version>1.2.1</version>
         </dependency>
+
+        <!-- Utilities -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>20.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+            <version>20160810</version>
+        </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <version>1.7.25</version>
         </dependency>
+
+        <!-- Test dependencies -->
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
diff --git a/src/main/java/com/google/firebase/FirebaseApp.java b/src/main/java/com/google/firebase/FirebaseApp.java
index 38f2c3d..d2c97d2 100644
--- a/src/main/java/com/google/firebase/FirebaseApp.java
+++ b/src/main/java/com/google/firebase/FirebaseApp.java
@@ -21,23 +21,24 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.OAuth2Credentials;
+import com.google.auth.oauth2.OAuth2Credentials.CredentialsChangedListener;
+import com.google.auth.oauth2.ServiceAccountCredentials;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.BaseEncoding;
-import com.google.firebase.auth.GoogleOAuthAccessToken;
-import com.google.firebase.internal.AuthStateListener;
 import com.google.firebase.internal.FirebaseAppStore;
 import com.google.firebase.internal.FirebaseExecutors;
 import com.google.firebase.internal.FirebaseService;
-import com.google.firebase.internal.GetTokenResult;
 import com.google.firebase.internal.NonNull;
 import com.google.firebase.internal.Nullable;
-import com.google.firebase.tasks.Continuation;
-import com.google.firebase.tasks.Task;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -49,7 +50,6 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -71,11 +71,9 @@ public class FirebaseApp {
   private static final Map<String, FirebaseApp> instances = new HashMap<>();
 
   public static final String DEFAULT_APP_NAME = "[DEFAULT]";
-  private static final long TOKEN_REFRESH_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(55);
 
-  static final TokenRefresher.Factory DEFAULT_TOKEN_REFRESHER_FACTORY =
+  private static final TokenRefresher.Factory DEFAULT_TOKEN_REFRESHER_FACTORY =
       new TokenRefresher.Factory();
-  static final Clock DEFAULT_CLOCK = new Clock();
 
   /**
    * Global lock for synchronizing all SDK-wide application state changes. Specifically, any
@@ -86,28 +84,21 @@ public class FirebaseApp {
   private final String name;
   private final FirebaseOptions options;
   private final TokenRefresher tokenRefresher;
-  private final Clock clock;
 
   private final AtomicBoolean deleted = new AtomicBoolean();
-  private final List<AuthStateListener> authStateListeners = new ArrayList<>();
-  private final AtomicReference<GetTokenResult> currentToken = new AtomicReference<>();
   private final Map<String, FirebaseService> services = new HashMap<>();
 
-  private Task<GoogleOAuthAccessToken> previousTokenTask;
-
   /**
    * Per application lock for synchronizing all internal FirebaseApp state changes.
    */
   private final Object lock = new Object();
 
   /** Default constructor. */
-  private FirebaseApp(String name, FirebaseOptions options,
-      TokenRefresher.Factory factory, Clock clock) {
+  private FirebaseApp(String name, FirebaseOptions options, TokenRefresher.Factory factory) {
     checkArgument(!Strings.isNullOrEmpty(name));
     this.name = name;
     this.options = checkNotNull(options);
     this.tokenRefresher = checkNotNull(factory).create(this);
-    this.clock = checkNotNull(clock);
   }
 
   /** Returns a list of all FirebaseApps. */
@@ -171,7 +162,7 @@ public class FirebaseApp {
   }
 
   /**
-   * A factory method to intialize a {@link FirebaseApp}.
+   * A factory method to initialize a {@link FirebaseApp}.
    *
    * @param options represents the global {@link FirebaseOptions}
    * @param name unique name for the app. It is an error to initialize an app with an already
@@ -180,11 +171,11 @@ public class FirebaseApp {
    * @throws IllegalStateException if an app with the same name has already been initialized.
    */
   public static FirebaseApp initializeApp(FirebaseOptions options, String name) {
-    return initializeApp(options, name, DEFAULT_TOKEN_REFRESHER_FACTORY, DEFAULT_CLOCK);
+    return initializeApp(options, name, DEFAULT_TOKEN_REFRESHER_FACTORY);
   }
 
   static FirebaseApp initializeApp(FirebaseOptions options, String name,
-      TokenRefresher.Factory tokenRefresherFactory, Clock clock) {
+      TokenRefresher.Factory tokenRefresherFactory) {
     FirebaseAppStore appStore = FirebaseAppStore.initialize();
     String normalizedName = normalize(name);
     final FirebaseApp firebaseApp;
@@ -193,7 +184,7 @@ public class FirebaseApp {
           !instances.containsKey(normalizedName),
           "FirebaseApp name " + normalizedName + " already exists!");
 
-      firebaseApp = new FirebaseApp(normalizedName, options, tokenRefresherFactory, clock);
+      firebaseApp = new FirebaseApp(normalizedName, options, tokenRefresherFactory);
       instances.put(normalizedName, firebaseApp);
     }
 
@@ -264,6 +255,31 @@ public class FirebaseApp {
     return options;
   }
 
+  /**
+   * Returns the Google Cloud project ID associated with this app.
+   *
+   * @return A string project ID or null.
+   */
+  @Nullable
+  String getProjectId() {
+    // Try to get project ID from user-specified options.
+    String projectId = options.getProjectId();
+
+    // Try to get project ID from the credentials.
+    if (Strings.isNullOrEmpty(projectId)) {
+      GoogleCredentials credentials = options.getCredentials();
+      if (credentials instanceof ServiceAccountCredentials) {
+        projectId = ((ServiceAccountCredentials) credentials).getProjectId();
+      }
+    }
+
+    // Try to get project ID from the environment.
+    if (Strings.isNullOrEmpty(projectId)) {
+      projectId = System.getenv("GCLOUD_PROJECT");
+    }
+    return projectId;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof FirebaseApp)) {
@@ -299,7 +315,6 @@ public class FirebaseApp {
         service.destroy();
       }
       services.clear();
-      authStateListeners.clear();
       tokenRefresher.cleanup();
     }
 
@@ -317,96 +332,10 @@ public class FirebaseApp {
     checkState(!deleted.get(), "FirebaseApp was deleted %s", this);
   }
 
-  private boolean refreshRequired(
-      @NonNull Task<GoogleOAuthAccessToken> previousTask, boolean forceRefresh) {
-    return (previousTask.isComplete()
-        && (forceRefresh || !previousTask.isSuccessful()
-        || previousTask.getResult().getExpiryTime() <= clock.now()));
-  }
-
-  /**
-   * Internal-only method to fetch a valid Service Account OAuth2 Token.
-   *
-   * @param forceRefresh force refreshes the token. Should only be set to <code>true</code> if the
-   *     token is invalidated out of band.
-   * @return a {@link Task}
-   */
-  Task<GetTokenResult> getToken(boolean forceRefresh) {
-    synchronized (lock) {
-      checkNotDeleted();
-      if (previousTokenTask == null || refreshRequired(previousTokenTask, forceRefresh)) {
-        previousTokenTask = options.getCredential().getAccessToken();
-      }
-
-      return previousTokenTask.continueWith(
-          new Continuation<GoogleOAuthAccessToken, GetTokenResult>() {
-            @Override
-            public GetTokenResult then(@NonNull Task<GoogleOAuthAccessToken> task)
-                throws Exception {
-              GoogleOAuthAccessToken googleOAuthToken = task.getResult();
-              GetTokenResult newToken = new GetTokenResult(googleOAuthToken.getAccessToken());
-              GetTokenResult oldToken = currentToken.get();
-              List<AuthStateListener> listenersCopy = null;
-              if (!newToken.equals(oldToken)) {
-                synchronized (lock) {
-                  if (deleted.get()) {
-                    return newToken;
-                  }
-
-                  // Grab the lock before compareAndSet to avoid a potential race
-                  // condition with addAuthStateListener. The same lock also ensures serial
-                  // access to the token refresher.
-                  if (currentToken.compareAndSet(oldToken, newToken)) {
-                    listenersCopy = ImmutableList.copyOf(authStateListeners);
-                    long refreshDelay  = googleOAuthToken.getExpiryTime() - clock.now()
-                        - TimeUnit.MINUTES.toMillis(5);
-                    if (refreshDelay > 0) {
-                      tokenRefresher.scheduleRefresh(refreshDelay);
-                    } else {
-                      logger.warn("Token expiry ({}) is less than 5 minutes in the future. Not "
-                          + "scheduling a proactive refresh.", googleOAuthToken.getAccessToken());
-                    }
-                  }
-                }
-              }
-
-              if (listenersCopy != null) {
-                for (AuthStateListener listener : listenersCopy) {
-                  listener.onAuthStateChanged(newToken);
-                }
-              }
-              return newToken;
-            }
-          });
-    }
-  }
-
   boolean isDefaultApp() {
     return DEFAULT_APP_NAME.equals(getName());
   }
 
-  void addAuthStateListener(@NonNull final AuthStateListener listener) {
-    GetTokenResult currentToken;
-    synchronized (lock) {
-      checkNotDeleted();
-      authStateListeners.add(checkNotNull(listener));
-      currentToken = this.currentToken.get();
-    }
-
-    if (currentToken != null) {
-      // Task has copied the authStateListeners before the listener was added.
-      // Notify this listener explicitly.
-      listener.onAuthStateChanged(currentToken);
-    }
-  }
-
-  void removeAuthStateListener(@NonNull AuthStateListener listener) {
-    synchronized (lock) {
-      checkNotDeleted();
-      authStateListeners.remove(checkNotNull(listener));
-    }
-  }
-
   void addService(FirebaseService service) {
     synchronized (lock) {
       checkNotDeleted();
@@ -423,17 +352,40 @@ public class FirebaseApp {
   }
 
   /**
-   * Utility class for scheduling proactive token refresh events.  Each FirebaseApp should have
-   * its own instance of this class. This class is not thread safe. The caller (FirebaseApp) must
-   * ensure that methods are called serially.
+   * Utility class for scheduling proactive token refresh events. Each FirebaseApp should have
+   * its own instance of this class. This class gets directly notified by GoogleCredentials
+   * whenever the underlying OAuth2 token changes. TokenRefresher schedules subsequent token
+   * refresh events when this happens.
+   *
+   * <p>This class is thread safe. It will handle only one token change event at a time. It also
+   * cancels any pending token refresh events, before scheduling a new one.
    */
-  static class TokenRefresher {
+  static class TokenRefresher implements CredentialsChangedListener {
 
-    private final FirebaseApp firebaseApp;
-    private ScheduledFuture<Task<GetTokenResult>> future;
+    private final GoogleCredentials credentials;
+    private ScheduledFuture<Void> future;
+    private boolean closed;
 
     TokenRefresher(FirebaseApp app) {
-      this.firebaseApp = checkNotNull(app);
+      this.credentials = app.getOptions().getCredentials();
+      this.credentials.addChangeListener(this);
+    }
+
+    @Override
+    public final synchronized void onChanged(OAuth2Credentials credentials) throws IOException {
+      if (closed) {
+        return;
+      }
+
+      AccessToken accessToken = credentials.getAccessToken();
+      long refreshDelay = accessToken.getExpirationTime().getTime()
+          - System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5);
+      if (refreshDelay > 0) {
+        scheduleRefresh(refreshDelay);
+      } else {
+        logger.warn("Token expiry ({}) is less than 5 minutes in the future. Not "
+            + "scheduling a proactive refresh.", accessToken.getExpirationTime());
+      }
     }
 
     /**
@@ -442,13 +394,15 @@ public class FirebaseApp {
      * @param delayMillis Duration in milliseconds, after which the token should be forcibly
      *     refreshed.
      */
-    final void scheduleRefresh(long delayMillis) {
+    private void scheduleRefresh(final long delayMillis) {
       cancelPrevious();
       scheduleNext(
-          new Callable<Task<GetTokenResult>>() {
+          new Callable<Void>() {
             @Override
-            public Task<GetTokenResult> call() throws Exception {
-              return firebaseApp.getToken(true);
+            public Void call() throws Exception {
+              logger.debug("Refreshing OAuth2 credential");
+              credentials.refresh();
+              return null;
             }
           },
           delayMillis);
@@ -460,7 +414,8 @@ public class FirebaseApp {
       }
     }
 
-    protected void scheduleNext(Callable<Task<GetTokenResult>> task, long delayMillis) {
+    protected void scheduleNext(Callable<Void> task, long delayMillis) {
+      logger.debug("Scheduling next token refresh in {} milliseconds", delayMillis);
       try {
         future =
             FirebaseExecutors.DEFAULT_SCHEDULED_EXECUTOR.schedule(
@@ -470,10 +425,9 @@ public class FirebaseApp {
       }
     }
 
-    protected void cleanup() {
-      if (future != null) {
-        future.cancel(true);
-      }
+    protected synchronized void cleanup() {
+      cancelPrevious();
+      closed = true;
     }
 
     static class Factory {
@@ -482,10 +436,4 @@ public class FirebaseApp {
       }
     }
   }
-
-  static class Clock {
-    long now() {
-      return System.currentTimeMillis();
-    }
-  }
 }
diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java
index 1c10939..3cfb131 100644
--- a/src/main/java/com/google/firebase/FirebaseOptions.java
+++ b/src/main/java/com/google/firebase/FirebaseOptions.java
@@ -22,9 +22,12 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import com.google.api.client.googleapis.util.Utils;
 import com.google.api.client.http.HttpTransport;
 import com.google.api.client.json.JsonFactory;
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.common.base.Strings;
 import com.google.firebase.auth.FirebaseCredential;
 import com.google.firebase.auth.FirebaseCredentials;
+import com.google.firebase.auth.internal.BaseCredential;
+import com.google.firebase.auth.internal.FirebaseCredentialsAdapter;
 import com.google.firebase.internal.NonNull;
 import com.google.firebase.internal.Nullable;
 
@@ -38,16 +41,19 @@ public final class FirebaseOptions {
 
   private final String databaseUrl;
   private final String storageBucket;
-  private final FirebaseCredential firebaseCredential;
+  private final GoogleCredentials credentials;
   private final Map<String, Object> databaseAuthVariableOverride;
+  private final String projectId;
   private final HttpTransport httpTransport;
   private final JsonFactory jsonFactory;
 
   private FirebaseOptions(@NonNull FirebaseOptions.Builder builder) {
-    this.firebaseCredential = checkNotNull(builder.firebaseCredential,
-        "FirebaseOptions must be initialized with setCredential().");
+    this.credentials = checkNotNull(builder.credentials,
+        "FirebaseOptions must be initialized with setCredentials().")
+        .createScoped(BaseCredential.FIREBASE_SCOPES);
     this.databaseUrl = builder.databaseUrl;
     this.databaseAuthVariableOverride = builder.databaseAuthVariableOverride;
+    this.projectId = builder.projectId;
     this.storageBucket = builder.storageBucket;
     this.httpTransport = checkNotNull(builder.httpTransport,
         "FirebaseOptions must be initialized with a non-null HttpTransport.");
@@ -73,8 +79,8 @@ public final class FirebaseOptions {
     return storageBucket;
   }
 
-  FirebaseCredential getCredential() {
-    return firebaseCredential;
+  GoogleCredentials getCredentials() {
+    return credentials;
   }
 
   /**
@@ -87,6 +93,10 @@ public final class FirebaseOptions {
     return databaseAuthVariableOverride;
   }
 
+  public String getProjectId() {
+    return projectId;
+  }
+
   /**
    * Returns the <code>HttpTransport</code> used to call remote HTTP endpoints. This transport is
    * used by all services of the SDK, except for FirebaseDatabase.
@@ -115,8 +125,9 @@ public final class FirebaseOptions {
 
     private String databaseUrl;
     private String storageBucket;
-    private FirebaseCredential firebaseCredential;
+    private GoogleCredentials credentials;
     private Map<String, Object> databaseAuthVariableOverride = new HashMap<>();
+    private String projectId;
     private HttpTransport httpTransport = Utils.getDefaultTransport();
     private JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
 
@@ -132,8 +143,9 @@ public final class FirebaseOptions {
     public Builder(FirebaseOptions options) {
       databaseUrl = options.databaseUrl;
       storageBucket = options.storageBucket;
-      firebaseCredential = options.firebaseCredential;
+      credentials = options.credentials;
       databaseAuthVariableOverride = options.databaseAuthVariableOverride;
+      projectId = options.projectId;
       httpTransport = options.httpTransport;
       jsonFactory = options.jsonFactory;
     }
@@ -170,6 +182,11 @@ public final class FirebaseOptions {
       return this;
     }
 
+    public Builder setCredentials(GoogleCredentials credentials) {
+      this.credentials = checkNotNull(credentials);
+      return this;
+    }
+
     /**
      * Sets the <code>FirebaseCredential</code> to use to authenticate the SDK.
      *
@@ -179,9 +196,15 @@ public final class FirebaseOptions {
      * @param credential A <code>FirebaseCredential</code> used to authenticate the SDK. See {@link
      *     FirebaseCredentials} for default implementations.
      * @return This <code>Builder</code> instance is returned so subsequent calls can be chained.
+     * @deprecated Use {@link FirebaseOptions.Builder#setCredentials(GoogleCredentials)}.
      */
     public Builder setCredential(@NonNull FirebaseCredential credential) {
-      firebaseCredential = checkNotNull(credential);
+      checkNotNull(credential);
+      if (credential instanceof BaseCredential) {
+        this.credentials = ((BaseCredential) credential).getGoogleCredentials();
+      } else {
+        this.credentials = new FirebaseCredentialsAdapter(credential);
+      }
       return this;
     }
 
@@ -209,6 +232,12 @@ public final class FirebaseOptions {
       return this;
     }
 
+    public Builder setProjectId(String projectId) {
+      checkArgument(!Strings.isNullOrEmpty(projectId), "Project ID must not be null or empty");
+      this.projectId = projectId;
+      return this;
+    }
+
     /**
      * Sets the <code>HttpTransport</code> used to make remote HTTP calls. A reasonable default
      * is used if not explicitly set. The transport specified by calling this method is
diff --git a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java
index a7d9c75..5931d24 100644
--- a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java
+++ b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java
@@ -16,12 +16,9 @@
 
 package com.google.firebase;
 
-import com.google.firebase.auth.FirebaseCredential;
-import com.google.firebase.internal.AuthStateListener;
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.firebase.internal.FirebaseService;
-import com.google.firebase.internal.GetTokenResult;
 import com.google.firebase.internal.NonNull;
-import com.google.firebase.tasks.Task;
 
 /**
  * Provides trampolines into package-private APIs used by components of Firebase. Intentionally
@@ -34,8 +31,12 @@ public final class ImplFirebaseTrampolines {
 
   private ImplFirebaseTrampolines() {}
 
-  public static FirebaseCredential getCredential(@NonNull FirebaseApp app) {
-    return app.getOptions().getCredential();
+  public static GoogleCredentials getCredentials(@NonNull FirebaseApp app) {
+    return app.getOptions().getCredentials();
+  }
+
+  public static String getProjectId(@NonNull FirebaseApp app) {
+    return app.getProjectId();
   }
 
   public static boolean isDefaultApp(@NonNull FirebaseApp app) {
@@ -50,20 +51,6 @@ public final class ImplFirebaseTrampolines {
     return FirebaseApp.getPersistenceKey(name, options);
   }
 
-  public static void addAuthStateChangeListener(
-      @NonNull FirebaseApp app, @NonNull AuthStateListener listener) {
-    app.addAuthStateListener(listener);
-  }
-
-  public static void removeAuthStateChangeListener(
-      @NonNull FirebaseApp app, @NonNull AuthStateListener listener) {
-    app.removeAuthStateListener(listener);
-  }
-
-  public static Task<GetTokenResult> getToken(@NonNull FirebaseApp app, boolean forceRefresh) {
-    return app.getToken(forceRefresh);
-  }
-
   public static <T extends FirebaseService> T getService(
       @NonNull FirebaseApp app, @NonNull String id, @NonNull Class<T> type) {
     return type.cast(app.getService(id));
diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuth.java b/src/main/java/com/google/firebase/auth/FirebaseAuth.java
index 9c9a9dd..e4afeae 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java
@@ -18,23 +18,23 @@ package com.google.firebase.auth;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
 
-import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
 import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
 import com.google.api.client.json.JsonFactory;
 import com.google.api.client.util.Clock;
 import com.google.api.core.ApiFuture;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.firebase.FirebaseApp;
-import com.google.firebase.FirebaseException;
 import com.google.firebase.ImplFirebaseTrampolines;
 import com.google.firebase.auth.UserRecord.CreateRequest;
 import com.google.firebase.auth.UserRecord.UpdateRequest;
 import com.google.firebase.auth.internal.FirebaseTokenFactory;
 import com.google.firebase.auth.internal.FirebaseTokenVerifier;
 import com.google.firebase.internal.FirebaseService;
-import com.google.firebase.internal.GetTokenResult;
 import com.google.firebase.internal.NonNull;
 import com.google.firebase.internal.TaskToApiFuture;
 import com.google.firebase.tasks.Continuation;
@@ -42,6 +42,8 @@ import com.google.firebase.tasks.Task;
 import com.google.firebase.tasks.Tasks;
 
 import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * This class is the entry point for all server-side Firebase Authentication actions.
@@ -53,11 +55,14 @@ import java.util.Map;
  */
 public class FirebaseAuth {
 
-  private final FirebaseApp firebaseApp;
+  private final GoogleCredentials credentials;
+  private final String projectId;
   private final GooglePublicKeysManager googlePublicKeysManager;
   private final Clock clock;
   private final JsonFactory jsonFactory;
   private final FirebaseUserManager userManager;
+  private final AtomicBoolean destroyed;
+  private final Object lock;
 
   private FirebaseAuth(FirebaseApp firebaseApp) {
     this(firebaseApp, FirebaseTokenVerifier.DEFAULT_KEY_MANAGER, Clock.SYSTEM);
@@ -70,12 +75,15 @@ public class FirebaseAuth {
   @VisibleForTesting
   FirebaseAuth(
       FirebaseApp firebaseApp, GooglePublicKeysManager googlePublicKeysManager, Clock clock) {
-    this.firebaseApp = firebaseApp;
+    this.credentials = ImplFirebaseTrampolines.getCredentials(firebaseApp);
+    this.projectId = ImplFirebaseTrampolines.getProjectId(firebaseApp);
     this.googlePublicKeysManager = googlePublicKeysManager;
     this.clock = clock;
     this.jsonFactory = firebaseApp.getOptions().getJsonFactory();
     this.userManager = new FirebaseUserManager(jsonFactory,
-        firebaseApp.getOptions().getHttpTransport());
+        firebaseApp.getOptions().getHttpTransport(), this.credentials);
+    this.destroyed = new AtomicBoolean(false);
+    this.lock = new Object();
   }
 
   /**
@@ -129,29 +137,23 @@ public class FirebaseAuth {
    */
   public Task<String> createCustomToken(
       final String uid, final Map<String, Object> developerClaims) {
-    FirebaseCredential credential = ImplFirebaseTrampolines.getCredential(firebaseApp);
-    if (!(credential instanceof FirebaseCredentials.CertCredential)) {
-      return Tasks.forException(
-          new FirebaseException(
-              "Must initialize FirebaseApp with a certificate credential to call "
-                  + "createCustomToken()"));
-    }
-
-    return ((FirebaseCredentials.CertCredential) credential)
-        .getCertificate()
-        .continueWith(
-            new Continuation<GoogleCredential, String>() {
-              @Override
-              public String then(@NonNull Task<GoogleCredential> task) throws Exception {
-                GoogleCredential baseCredential = task.getResult();
-                FirebaseTokenFactory tokenFactory = FirebaseTokenFactory.getInstance();
-                return tokenFactory.createSignedCustomAuthTokenForUser(
-                    uid,
-                    developerClaims,
-                    baseCredential.getServiceAccountId(),
-                    baseCredential.getServiceAccountPrivateKey());
-              }
-            });
+    checkNotDestroyed();
+    checkState(credentials instanceof ServiceAccountCredentials,
+        "Must initialize FirebaseApp with a service account credential to call "
+            + "createCustomToken()");
+
+    final ServiceAccountCredentials serviceAccount = (ServiceAccountCredentials) credentials;
+    return Tasks.call(new Callable<String>() {
+      @Override
+      public String call() throws Exception {
+        FirebaseTokenFactory tokenFactory = FirebaseTokenFactory.getInstance();
+        return tokenFactory.createSignedCustomAuthTokenForUser(
+            uid,
+            developerClaims,
+            serviceAccount.getClientEmail(),
+            serviceAccount.getPrivateKey());
+      }
+    });
   }
 
   /**
@@ -194,15 +196,10 @@ public class FirebaseAuth {
    * @deprecated Use {@link #verifyIdTokenAsync(String)}
    */
   public Task<FirebaseToken> verifyIdToken(final String token) {
-    FirebaseCredential credential = ImplFirebaseTrampolines.getCredential(firebaseApp);
-    if (!(credential instanceof FirebaseCredentials.CertCredential)) {
-      return Tasks.forException(
-          new FirebaseException(
-              "Must initialize FirebaseApp with a certificate credential to call "
-                  + "verifyIdToken()"));
-    }
-    return ((FirebaseCredentials.CertCredential) credential)
-        .getProjectId()
+    checkNotDestroyed();
+    checkState(!Strings.isNullOrEmpty(projectId),
+        "Must initialize FirebaseApp with a project ID to call verifyIdToken()");
+    return Tasks.forResult(projectId)
         .continueWith(
             new Continuation<String, FirebaseToken>() {
               @Override
@@ -258,14 +255,14 @@ public class FirebaseAuth {
    * @deprecated Use {@link #getUserAsync(String)}
    */
   public Task<UserRecord> getUser(final String uid) {
+    checkNotDestroyed();
     checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
-    return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
-        new Continuation<GetTokenResult, UserRecord>() {
-          @Override
-          public UserRecord then(Task<GetTokenResult> task) throws Exception {
-            return userManager.getUserById(uid, task.getResult().getToken());
-          }
-        });
+    return Tasks.call(new Callable<UserRecord>() {
+      @Override
+      public UserRecord call() throws Exception {
+        return userManager.getUserById(uid);
+      }
+    });
   }
 
   /**
@@ -292,14 +289,14 @@ public class FirebaseAuth {
    * @deprecated Use {@link #getUserByEmailAsync(String)}
    */
   public Task<UserRecord> getUserByEmail(final String email) {
+    checkNotDestroyed();
     checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty");
-    return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
-        new Continuation<GetTokenResult, UserRecord>() {
-          @Override
-          public UserRecord then(Task<GetTokenResult> task) throws Exception {
-            return userManager.getUserByEmail(email, task.getResult().getToken());
-          }
-        });
+    return Tasks.call(new Callable<UserRecord>() {
+      @Override
+      public UserRecord call() throws Exception {
+        return userManager.getUserByEmail(email);
+      }
+    });
   }
 
   /**
@@ -326,14 +323,14 @@ public class FirebaseAuth {
    * @deprecated Use {@link #getUserByPhoneNumberAsync(String)}
    */
   public Task<UserRecord> getUserByPhoneNumber(final String phoneNumber) {
+    checkNotDestroyed();
     checkArgument(!Strings.isNullOrEmpty(phoneNumber), "phone number must not be null or empty");
-    return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
-        new Continuation<GetTokenResult, UserRecord>() {
-          @Override
-          public UserRecord then(Task<GetTokenResult> task) throws Exception {
-            return userManager.getUserByPhoneNumber(phoneNumber, task.getResult().getToken());
-          }
-        });
+    return Tasks.call(new Callable<UserRecord>() {
+      @Override
+      public UserRecord call() throws Exception {
+        return userManager.getUserByPhoneNumber(phoneNumber);
+      }
+    });
   }
 
   /**
@@ -360,15 +357,15 @@ public class FirebaseAuth {
    * @deprecated Use {@link #createUserAsync(CreateRequest)}
    */
   public Task<UserRecord> createUser(final CreateRequest request) {
+    checkNotDestroyed();
     checkNotNull(request, "create request must not be null");
-    return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
-        new Continuation<GetTokenResult, UserRecord>() {
-          @Override
-          public UserRecord then(Task<GetTokenResult> task) throws Exception {
-            String uid = userManager.createUser(request, task.getResult().getToken());
-            return userManager.getUserById(uid, task.getResult().getToken());
-          }
-        });
+    return Tasks.call(new Callable<UserRecord>() {
+      @Override
+      public UserRecord call() throws Exception {
+        String uid = userManager.createUser(request);
+        return userManager.getUserById(uid);
+      }
+    });
   }
 
   /**
@@ -396,15 +393,15 @@ public class FirebaseAuth {
    * @deprecated Use {@link #updateUserAsync(UpdateRequest)}
    */
   public Task<UserRecord> updateUser(final UpdateRequest request) {
+    checkNotDestroyed();
     checkNotNull(request, "update request must not be null");
-    return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
-        new Continuation<GetTokenResult, UserRecord>() {
-          @Override
-          public UserRecord then(Task<GetTokenResult> task) throws Exception {
-            userManager.updateUser(request, task.getResult().getToken());
-            return userManager.getUserById(request.getUid(), task.getResult().getToken());
-          }
-        });
+    return Tasks.call(new Callable<UserRecord>() {
+      @Override
+      public UserRecord call() throws Exception {
+        userManager.updateUser(request);
+        return userManager.getUserById(request.getUid());
+      }
+    });
   }
 
   /**
@@ -432,15 +429,28 @@ public class FirebaseAuth {
    * @deprecated Use {@link #deleteUserAsync(String)}
    */
   public Task<Void> deleteUser(final String uid) {
+    checkNotDestroyed();
     checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
-    return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
-        new Continuation<GetTokenResult, Void>() {
-          @Override
-          public Void then(Task<GetTokenResult> task) throws Exception {
-            userManager.deleteUser(uid, task.getResult().getToken());
-            return null;
-          }
-        });
+    return Tasks.call(new Callable<Void>() {
+      @Override
+      public Void call() throws Exception {
+        userManager.deleteUser(uid);
+        return null;
+      }
+    });
+  }
+
+  private void checkNotDestroyed() {
+    synchronized (lock) {
+      checkState(!destroyed.get(), "FirebaseAuth instance is no longer alive. This happens when "
+          + "the parent FirebaseApp instance has been deleted.");
+    }
+  }
+
+  private void destroy() {
+    synchronized (lock) {
+      destroyed.set(true);
+    }
   }
 
   /**
@@ -466,9 +476,7 @@ public class FirebaseAuth {
 
     @Override
     public void destroy() {
-      // NOTE: We don't explicitly tear down anything here, but public methods of FirebaseAuth
-      // will now fail because calls to getCredential() and getToken() will hit FirebaseApp,
-      // which will throw once the app is deleted.
+      instance.destroy();
     }
   }
 }
diff --git a/src/main/java/com/google/firebase/auth/FirebaseCredential.java b/src/main/java/com/google/firebase/auth/FirebaseCredential.java
index 9825eac..3b05df8 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseCredential.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseCredential.java
@@ -22,6 +22,8 @@ import com.google.firebase.tasks.Task;
  * Provides Google OAuth2 access tokens used to authenticate with Firebase services. In most cases,
  * you will not need to implement this yourself and can instead use the default implementations
  * provided by {@link FirebaseCredentials}.
+ *
+ * @deprecated Use GoogleCredentials
  */
 public interface FirebaseCredential {
 
diff --git a/src/main/java/com/google/firebase/auth/FirebaseCredentials.java b/src/main/java/com/google/firebase/auth/FirebaseCredentials.java
index 072fd58..d513599 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseCredentials.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseCredentials.java
@@ -16,58 +16,34 @@
 
 package com.google.firebase.auth;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
 import com.google.api.client.googleapis.util.Utils;
 import com.google.api.client.http.HttpTransport;
 import com.google.api.client.json.JsonFactory;
-import com.google.common.collect.ImmutableList;
-import com.google.common.io.CharStreams;
+import com.google.auth.http.HttpTransportFactory;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import com.google.auth.oauth2.UserCredentials;
+import com.google.common.base.Strings;
+import com.google.firebase.auth.internal.BaseCredential;
 import com.google.firebase.internal.NonNull;
-import com.google.firebase.tasks.Continuation;
-import com.google.firebase.tasks.Task;
-import com.google.firebase.tasks.Tasks;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-import org.json.JSONException;
-import org.json.JSONObject;
 
 /**
  * Standard {@link FirebaseCredential} implementations for use with {@link
  * com.google.firebase.FirebaseOptions}.
+ *
+ * @deprecated Use GoogleCredentials.
  */
 public class FirebaseCredentials {
 
-  private static final List<String> FIREBASE_SCOPES =
-      ImmutableList.of(
-          // Enables access to Firebase Realtime Database.
-          "https://www.googleapis.com/auth/firebase.database",
-
-          // Enables access to the email address associated with a project.
-          "https://www.googleapis.com/auth/userinfo.email",
-
-          // Enables access to Google Identity Toolkit (for user management APIs).
-          "https://www.googleapis.com/auth/identitytoolkit",
-
-          // Enables access to Google Cloud Storage.
-          "https://www.googleapis.com/auth/devstorage.full_control");
-
   private FirebaseCredentials() {
   }
 
-  private static String streamToString(InputStream inputStream) throws IOException {
-    InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
-    return CharStreams.toString(reader);
-  }
-
   /**
    * Returns a {@link FirebaseCredential} based on Google Application Default Credentials which can
    * be used to authenticate the SDK.
@@ -109,7 +85,12 @@ public class FirebaseCredentials {
   @NonNull
   public static FirebaseCredential applicationDefault(
       HttpTransport transport, JsonFactory jsonFactory) {
-    return new ApplicationDefaultCredential(transport, jsonFactory);
+    try {
+      return new ApplicationDefaultCredential(transport);
+    } catch (IOException e) {
+      // To prevent a breaking API change, we throw an unchecked exception.
+      throw new RuntimeException(e);
+    }
   }
 
   /**
@@ -153,7 +134,11 @@ public class FirebaseCredentials {
   @NonNull
   public static FirebaseCredential fromCertificate(InputStream serviceAccount,
       HttpTransport transport, JsonFactory jsonFactory) throws IOException {
-    return new CertCredential(serviceAccount, transport, jsonFactory);
+    ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(
+        serviceAccount, wrap(transport));
+    checkArgument(!Strings.isNullOrEmpty(credentials.getProjectId()),
+        "Failed to parse service account: 'project_id' must be set");
+    return new CertCredential(credentials);
   }
 
   /**
@@ -196,162 +181,27 @@ public class FirebaseCredentials {
   @NonNull
   public static FirebaseCredential fromRefreshToken(final InputStream refreshToken,
       HttpTransport transport, JsonFactory jsonFactory) throws IOException {
-    return new RefreshTokenCredential(refreshToken, transport, jsonFactory);
-  }
-
-  /**
-   * Helper class that implements {@link FirebaseCredential} on top of {@link GoogleCredential} and
-   * provides caching of access tokens and credentials.
-   */
-  abstract static class BaseCredential implements FirebaseCredential {
-
-    final HttpTransport transport;
-    final JsonFactory jsonFactory;
-    private GoogleCredential googleCredential;
-
-    BaseCredential(HttpTransport transport, JsonFactory jsonFactory) {
-      this.transport = checkNotNull(transport, "HttpTransport must not be null");
-      this.jsonFactory = checkNotNull(jsonFactory, "JsonFactory must not be null");
-    }
-
-    /** Retrieves a GoogleCredential. Should not use caching. */
-    abstract GoogleCredential fetchCredential() throws IOException;
-
-    /**
-     * Returns the associated GoogleCredential for this class. This implementation is cached by
-     * default.
-     */
-    final Task<GoogleCredential> getCertificate() {
-      synchronized (this) {
-        if (googleCredential != null) {
-          return Tasks.forResult(googleCredential);
-        }
-      }
-
-      return Tasks.call(
-          new Callable<GoogleCredential>() {
-            @Override
-            public GoogleCredential call() throws Exception {
-              // Retrieve a new credential. This is a network operation that can be repeated and is
-              // done outside of the lock.
-              GoogleCredential credential = fetchCredential();
-              synchronized (BaseCredential.this) {
-                googleCredential = credential;
-              }
-              return credential;
-            }
-          });
-    }
-
-    abstract GoogleOAuthAccessToken fetchToken(GoogleCredential credential) throws IOException;
-
-    /**
-     * Returns an access token for this credential. Does not cache tokens.
-     */
-    @Override
-    public final Task<GoogleOAuthAccessToken> getAccessToken() {
-      return getCertificate()
-          .continueWith(new Continuation<GoogleCredential, GoogleOAuthAccessToken>() {
-            @Override
-            public GoogleOAuthAccessToken then(@NonNull Task<GoogleCredential> task)
-                throws Exception {
-              return fetchToken(task.getResult());
-            }
-          });
-    }
+    return new RefreshTokenCredential(refreshToken, transport);
   }
 
   static class CertCredential extends BaseCredential {
 
-    private final String jsonData;
-    private final String projectId;
-
-    CertCredential(InputStream inputStream, HttpTransport transport,
-        JsonFactory jsonFactory) throws IOException {
-      super(transport, jsonFactory);
-      jsonData = streamToString(checkNotNull(inputStream));
-      JSONObject jsonObject = new JSONObject(jsonData);
-      try {
-        projectId = jsonObject.getString("project_id");
-      } catch (JSONException e) {
-        throw new IOException("Failed to parse service account: 'project_id' must be set", e);
-      }
-    }
-
-    @Override
-    GoogleCredential fetchCredential() throws IOException {
-      GoogleCredential firebaseCredential =
-          GoogleCredential.fromStream(
-              new ByteArrayInputStream(jsonData.getBytes("UTF-8")), transport, jsonFactory);
-
-      if (firebaseCredential.getServiceAccountId() == null) {
-        throw new IOException(
-            "Error reading credentials from stream, 'type' value 'service_account' not "
-                + "recognized. Expecting 'authorized_user'.");
-      }
-
-      return firebaseCredential.createScoped(FIREBASE_SCOPES);
-    }
-
-    @Override
-    GoogleOAuthAccessToken fetchToken(GoogleCredential credential) throws IOException {
-      credential.refreshToken();
-      return newAccessToken(credential);
-    }
-
-    Task<String> getProjectId() {
-      return Tasks.forResult(projectId);
+    CertCredential(ServiceAccountCredentials credentials) throws IOException {
+      super(credentials);
     }
   }
 
   static class ApplicationDefaultCredential extends BaseCredential {
 
-    ApplicationDefaultCredential(HttpTransport transport, JsonFactory jsonFactory) {
-      super(transport, jsonFactory);
-    }
-
-    @Override
-    GoogleCredential fetchCredential() throws IOException {
-      return GoogleCredential.getApplicationDefault(transport, jsonFactory)
-          .createScoped(FIREBASE_SCOPES);
-    }
-
-    @Override
-    GoogleOAuthAccessToken fetchToken(GoogleCredential credential) throws IOException {
-      credential.refreshToken();
-      return newAccessToken(credential);
+    ApplicationDefaultCredential(HttpTransport transport) throws IOException {
+      super(GoogleCredentials.getApplicationDefault(wrap(transport)));
     }
   }
 
   static class RefreshTokenCredential extends BaseCredential {
 
-    private final String jsonData;
-
-    RefreshTokenCredential(InputStream inputStream, HttpTransport transport,
-        JsonFactory jsonFactory) throws IOException {
-      super(transport, jsonFactory);
-      jsonData = streamToString(checkNotNull(inputStream));
-    }
-
-    @Override
-    GoogleCredential fetchCredential() throws IOException {
-      GoogleCredential credential =
-          GoogleCredential.fromStream(
-              new ByteArrayInputStream(jsonData.getBytes("UTF-8")), transport, jsonFactory);
-
-      if (credential.getServiceAccountId() != null) {
-        throw new IOException(
-            "Error reading credentials from stream, 'type' value 'authorized_user' not "
-                + "recognized. Expecting 'service_account'.");
-      }
-
-      return credential;
-    }
-
-    @Override
-    GoogleOAuthAccessToken fetchToken(GoogleCredential credential) throws IOException {
-      credential.refreshToken();
-      return newAccessToken(credential);
+    RefreshTokenCredential(InputStream inputStream, HttpTransport transport) throws IOException {
+      super(UserCredentials.fromStream(inputStream, wrap(transport)));
     }
   }
 
@@ -361,9 +211,13 @@ public class FirebaseCredentials {
         applicationDefault(Utils.getDefaultTransport(), Utils.getDefaultJsonFactory());
   }
 
-  static GoogleOAuthAccessToken newAccessToken(GoogleCredential credential) {
-    checkNotNull(credential);
-    return new GoogleOAuthAccessToken(credential.getAccessToken(),
-        credential.getExpirationTimeMilliseconds());
+  private static HttpTransportFactory wrap(final HttpTransport transport) {
+    checkNotNull(transport, "HttpTransport must not be null");
+    return new HttpTransportFactory() {
+      @Override
+      public HttpTransport create() {
+        return transport;
+      }
+    };
   }
 }
diff --git a/src/main/java/com/google/firebase/auth/FirebaseToken.java b/src/main/java/com/google/firebase/auth/FirebaseToken.java
index 15dd040..1726250 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseToken.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseToken.java
@@ -51,7 +51,7 @@ public final class FirebaseToken {
     } catch (IOException e) {
       throw new IOException(
           "Decoding Firebase ID token failed. Make sure you passed the entire string JWT "
-              + "which represents an ID token. See https://firebase.google.com/docs/auth/admin/ "
+              + "which represents an ID token. See https://firebase.google.com/docs/auth/admin/"
               + "verify-id-tokens for details on how to retrieve an ID token.",
           e);
     }
diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
index df56a8c..346b35d 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
@@ -29,6 +29,8 @@ import com.google.api.client.http.json.JsonHttpContent;
 import com.google.api.client.json.GenericJson;
 import com.google.api.client.json.JsonFactory;
 import com.google.api.client.json.JsonObjectParser;
+import com.google.auth.http.HttpCredentialsAdapter;
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
@@ -72,9 +74,10 @@ class FirebaseUserManager {
    * @param jsonFactory JsonFactory instance used to transform Java objects into JSON and back.
    * @param transport HttpTransport used to make REST API calls.
    */
-  FirebaseUserManager(JsonFactory jsonFactory, HttpTransport transport) {
+  FirebaseUserManager(JsonFactory jsonFactory, HttpTransport transport,
+      GoogleCredentials credentials) {
     this.jsonFactory = checkNotNull(jsonFactory, "jsonFactory must not be null");
-    this.requestFactory = transport.createRequestFactory();
+    this.requestFactory = transport.createRequestFactory(new HttpCredentialsAdapter(credentials));
   }
 
   @VisibleForTesting
@@ -82,13 +85,12 @@ class FirebaseUserManager {
     this.interceptor = interceptor;
   }
 
-  UserRecord getUserById(String uid, String token) throws FirebaseAuthException {
+  UserRecord getUserById(String uid) throws FirebaseAuthException {
     final Map<String, Object> payload = ImmutableMap.<String, Object>of(
         "localId", ImmutableList.of(uid));
     GetAccountInfoResponse response;
     try {
-      response = post("getAccountInfo", token, payload,
-          GetAccountInfoResponse.class);
+      response = post("getAccountInfo", payload, GetAccountInfoResponse.class);
     } catch (IOException e) {
       throw new FirebaseAuthException(INTERNAL_ERROR,
           "IO error while retrieving user with ID: " + uid, e);
@@ -101,12 +103,12 @@ class FirebaseUserManager {
     return new UserRecord(response.getUsers().get(0));
   }
 
-  UserRecord getUserByEmail(String email, String token) throws FirebaseAuthException {
+  UserRecord getUserByEmail(String email) throws FirebaseAuthException {
     final Map<String, Object> payload = ImmutableMap.<String, Object>of(
         "email", ImmutableList.of(email));
     GetAccountInfoResponse response;
     try {
-      response = post("getAccountInfo", token, payload, GetAccountInfoResponse.class);
+      response = post("getAccountInfo", payload, GetAccountInfoResponse.class);
     } catch (IOException e) {
       throw new FirebaseAuthException(INTERNAL_ERROR,
           "IO error while retrieving user with email: " + email, e);
@@ -119,12 +121,12 @@ class FirebaseUserManager {
     return new UserRecord(response.getUsers().get(0));
   }
 
-  UserRecord getUserByPhoneNumber(String phoneNumber, String token) throws FirebaseAuthException {
+  UserRecord getUserByPhoneNumber(String phoneNumber) throws FirebaseAuthException {
     final Map<String, Object> payload = ImmutableMap.<String, Object>of(
         "phoneNumber", ImmutableList.of(phoneNumber));
     GetAccountInfoResponse response;
     try {
-      response = post("getAccountInfo", token, payload, GetAccountInfoResponse.class);
+      response = post("getAccountInfo", payload, GetAccountInfoResponse.class);
     } catch (IOException e) {
       throw new FirebaseAuthException(INTERNAL_ERROR,
           "IO error while retrieving user with phone number: " + phoneNumber, e);
@@ -137,10 +139,10 @@ class FirebaseUserManager {
     return new UserRecord(response.getUsers().get(0));
   }
 
-  String createUser(CreateRequest request, String token) throws FirebaseAuthException {
+  String createUser(CreateRequest request) throws FirebaseAuthException {
     GenericJson response;
     try {
-      response = post("signupNewUser", token, request.getProperties(), GenericJson.class);
+      response = post("signupNewUser", request.getProperties(), GenericJson.class);
     } catch (IOException e) {
       throw new FirebaseAuthException(USER_CREATE_ERROR,
           "IO error while creating user account", e);
@@ -155,10 +157,10 @@ class FirebaseUserManager {
     throw new FirebaseAuthException(USER_CREATE_ERROR, "Failed to create new user");
   }
 
-  void updateUser(UpdateRequest request, String token) throws FirebaseAuthException {
+  void updateUser(UpdateRequest request) throws FirebaseAuthException {
     GenericJson response;
     try {
-      response = post("setAccountInfo", token, request.getProperties(), GenericJson.class);
+      response = post("setAccountInfo", request.getProperties(), GenericJson.class);
     } catch (IOException e) {
       throw new FirebaseAuthException(USER_UPDATE_ERROR,
           "IO error while updating user: " + request.getUid(), e);
@@ -170,11 +172,11 @@ class FirebaseUserManager {
     }
   }
 
-  void deleteUser(String uid, String token) throws FirebaseAuthException {
+  void deleteUser(String uid) throws FirebaseAuthException {
     final Map<String, Object> payload = ImmutableMap.<String, Object>of("localId", uid);
     GenericJson response;
     try {
-      response = post("deleteAccount", token, payload, GenericJson.class);
+      response = post("deleteAccount", payload, GenericJson.class);
     } catch (IOException e) {
       throw new FirebaseAuthException(USER_DELETE_ERROR,
           "IO error while deleting user: " + uid, e);
@@ -185,9 +187,8 @@ class FirebaseUserManager {
     }
   }
 
-  private <T> T post(String path, String token, Object content, Class<T> clazz) throws IOException {
+  private <T> T post(String path, Object content, Class<T> clazz) throws IOException {
     checkArgument(!Strings.isNullOrEmpty(path), "path must not be null or empty");
-    checkArgument(!Strings.isNullOrEmpty(token), "OAuth token must not be null or empty");
     checkNotNull(content, "content must not be null");
     checkNotNull(clazz, "response class must not be null");
 
@@ -195,8 +196,7 @@ class FirebaseUserManager {
     HttpRequest request = requestFactory.buildPostRequest(url,
         new JsonHttpContent(jsonFactory, content));
     request.setParser(new JsonObjectParser(jsonFactory));
-    request.getHeaders().setAuthorization("Bearer " + token)
-        .set(CLIENT_VERSION_HEADER, clientVersion);
+    request.getHeaders().set(CLIENT_VERSION_HEADER, clientVersion);
     request.setResponseInterceptor(interceptor);
     HttpResponse response = request.execute();
     try {
diff --git a/src/main/java/com/google/firebase/auth/GoogleOAuthAccessToken.java b/src/main/java/com/google/firebase/auth/GoogleOAuthAccessToken.java
index 59830de..85e6021 100644
--- a/src/main/java/com/google/firebase/auth/GoogleOAuthAccessToken.java
+++ b/src/main/java/com/google/firebase/auth/GoogleOAuthAccessToken.java
@@ -23,6 +23,8 @@ import com.google.common.base.Strings;
 /**
  * Represents an OAuth access token, which can be used to access Firebase and other qualified
  * Google APIs. Encapsulates both the token string, and its expiration time.
+ *
+ * @deprecated Use GoogleCredentials and associated classes.
  */
 public class GoogleOAuthAccessToken {
 
diff --git a/src/main/java/com/google/firebase/auth/internal/BaseCredential.java b/src/main/java/com/google/firebase/auth/internal/BaseCredential.java
new file mode 100644
index 0000000..c5b5be6
--- /dev/null
+++ b/src/main/java/com/google/firebase/auth/internal/BaseCredential.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.auth.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.common.collect.ImmutableList;
+import com.google.firebase.auth.FirebaseCredential;
+import com.google.firebase.auth.GoogleOAuthAccessToken;
+import com.google.firebase.tasks.Task;
+import com.google.firebase.tasks.Tasks;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Internal base class for built-in FirebaseCredential implementations.
+ */
+public abstract class BaseCredential implements FirebaseCredential {
+
+  public static final List<String> FIREBASE_SCOPES =
+      ImmutableList.of(
+          // Enables access to Firebase Realtime Database.
+          "https://www.googleapis.com/auth/firebase.database",
+
+          // Enables access to the email address associated with a project.
+          "https://www.googleapis.com/auth/userinfo.email",
+
+          // Enables access to Google Identity Toolkit (for user management APIs).
+          "https://www.googleapis.com/auth/identitytoolkit",
+
+          // Enables access to Google Cloud Storage.
+          "https://www.googleapis.com/auth/devstorage.full_control");
+
+  private final GoogleCredentials googleCredentials;
+
+  public BaseCredential(GoogleCredentials googleCredentials) {
+    this.googleCredentials = checkNotNull(googleCredentials).createScoped(FIREBASE_SCOPES);
+  }
+
+  public final GoogleCredentials getGoogleCredentials() {
+    return googleCredentials;
+  }
+
+  @Override
+  public Task<GoogleOAuthAccessToken> getAccessToken() {
+    return Tasks.call(new Callable<GoogleOAuthAccessToken>() {
+      @Override
+      public GoogleOAuthAccessToken call() throws Exception {
+        AccessToken accessToken = googleCredentials.refreshAccessToken();
+        return new GoogleOAuthAccessToken(accessToken.getTokenValue(),
+            accessToken.getExpirationTime().getTime());
+      }
+    });
+  }
+
+}
diff --git a/src/main/java/com/google/firebase/auth/internal/FirebaseCredentialsAdapter.java b/src/main/java/com/google/firebase/auth/internal/FirebaseCredentialsAdapter.java
new file mode 100644
index 0000000..e1b069e
--- /dev/null
+++ b/src/main/java/com/google/firebase/auth/internal/FirebaseCredentialsAdapter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.auth.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.firebase.auth.FirebaseCredential;
+import com.google.firebase.auth.GoogleOAuthAccessToken;
+import com.google.firebase.tasks.Tasks;
+import java.io.IOException;
+import java.util.Date;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * An adapter for converting custom {@link FirebaseCredential} implementations into
+ * GoogleCredentials.
+ */
+public final class FirebaseCredentialsAdapter extends GoogleCredentials {
+
+  private final FirebaseCredential credential;
+
+  public FirebaseCredentialsAdapter(FirebaseCredential credential) {
+    this.credential = checkNotNull(credential);
+  }
+
+  @Override
+  public AccessToken refreshAccessToken() throws IOException {
+    try {
+      GoogleOAuthAccessToken token = Tasks.await(credential.getAccessToken());
+      return new AccessToken(token.getAccessToken(), new Date(token.getExpiryTime()));
+    } catch (ExecutionException | InterruptedException e) {
+      throw new IOException("Error while obtaining OAuth2 token", e);
+    }
+  }
+}
diff --git a/src/main/java/com/google/firebase/cloud/StorageClient.java b/src/main/java/com/google/firebase/cloud/StorageClient.java
index 0c3c67d..24668be 100644
--- a/src/main/java/com/google/firebase/cloud/StorageClient.java
+++ b/src/main/java/com/google/firebase/cloud/StorageClient.java
@@ -25,7 +25,6 @@ import com.google.cloud.storage.StorageOptions;
 import com.google.common.base.Strings;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.ImplFirebaseTrampolines;
-import com.google.firebase.internal.FirebaseCloudCredentials;
 import com.google.firebase.internal.FirebaseService;
 
 /**
@@ -45,7 +44,7 @@ public class StorageClient {
   private StorageClient(FirebaseApp app) {
     this.app = checkNotNull(app, "FirebaseApp must not be null");
     this.storage = StorageOptions.newBuilder()
-        .setCredentials(new FirebaseCloudCredentials(app))
+        .setCredentials(ImplFirebaseTrampolines.getCredentials(app))
         .build()
         .getService();
   }
diff --git a/src/main/java/com/google/firebase/database/core/AuthTokenProvider.java b/src/main/java/com/google/firebase/database/core/AuthTokenProvider.java
index badbb91..15da9e7 100644
--- a/src/main/java/com/google/firebase/database/core/AuthTokenProvider.java
+++ b/src/main/java/com/google/firebase/database/core/AuthTokenProvider.java
@@ -34,13 +34,6 @@ public interface AuthTokenProvider {
    */
   void addTokenChangeListener(TokenChangeListener listener);
 
-  /**
-   * Removes a previously-registered TokenChangeListener.
-   *
-   * @param listener Listener to be removed.
-   */
-  void removeTokenChangeListener(TokenChangeListener listener);
-
   interface GetTokenCompletionListener {
 
     /**
@@ -66,9 +59,5 @@ public interface AuthTokenProvider {
      */
     void onTokenChange(String token);
 
-    // TODO: Remove this once AndroidAuthTokenProvider is updated to call
-    // the other method.
-
-    void onTokenChange();
   }
 }
diff --git a/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java b/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java
index 79015ba..7301b07 100644
--- a/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java
+++ b/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java
@@ -18,100 +18,100 @@ package com.google.firebase.database.core;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.OAuth2Credentials;
+import com.google.auth.oauth2.OAuth2Credentials.CredentialsChangedListener;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.ImplFirebaseTrampolines;
 import com.google.firebase.database.util.GAuthToken;
-import com.google.firebase.internal.AuthStateListener;
-import com.google.firebase.internal.GetTokenResult;
-import com.google.firebase.internal.NonNull;
-import com.google.firebase.tasks.OnCompleteListener;
-import com.google.firebase.tasks.Task;
 
+import java.io.IOException;
 import java.util.Map;
 import java.util.concurrent.ScheduledExecutorService;
 
 public class JvmAuthTokenProvider implements AuthTokenProvider {
 
+  private final GoogleCredentials credentials;
+  private final Map<String, Object> authVariable;
   private final ScheduledExecutorService executorService;
-  private final FirebaseApp firebaseApp;
 
-  public JvmAuthTokenProvider(FirebaseApp firebaseApp, ScheduledExecutorService executorService) {
+  JvmAuthTokenProvider(FirebaseApp firebaseApp, ScheduledExecutorService executorService) {
+    this.credentials = ImplFirebaseTrampolines.getCredentials(firebaseApp);
+    this.authVariable = firebaseApp.getOptions().getDatabaseAuthVariableOverride();
     this.executorService = executorService;
-    this.firebaseApp = firebaseApp;
-  }
-
-  private static String wrapOAuthToken(FirebaseApp firebaseApp, GetTokenResult result) {
-    String oauthToken = result.getToken();
-    if (oauthToken == null) {
-      // This shouldn't happen in the actual production SDK, but can happen in tests.
-      return null;
-    } else {
-      Map<String, Object> authVariable = firebaseApp.getOptions().getDatabaseAuthVariableOverride();
-      GAuthToken googleAuthToken = new GAuthToken(oauthToken, authVariable);
-      return googleAuthToken.serializeToString();
-    }
   }
 
   @Override
   public void getToken(boolean forceRefresh, final GetTokenCompletionListener listener) {
-    ImplFirebaseTrampolines.getToken(firebaseApp, forceRefresh)
-        .addOnCompleteListener(
-            this.executorService,
-            new OnCompleteListener<GetTokenResult>() {
-              @Override
-              public void onComplete(@NonNull Task<GetTokenResult> task) {
-                if (task.isSuccessful()) {
-                  listener.onSuccess(wrapOAuthToken(firebaseApp, task.getResult()));
-                } else {
-                  listener.onError(task.getException().toString());
-                }
-              }
-            });
+    try {
+      if (forceRefresh) {
+        credentials.refresh();
+      }
+
+      // The typical way to use a GoogleCredentials instance is to call its getRequestMetadata(),
+      // and include the metadata in your request. Since we are accessing the token directly via
+      // getAccessToken(), we must first call getRequestMetadata() to ensure the token is available
+      // (refreshed if necessary).
+      credentials.getRequestMetadata();
+
+      AccessToken accessToken = credentials.getAccessToken();
+      listener.onSuccess(wrapOAuthToken(accessToken, authVariable));
+    } catch (IOException e) {
+      listener.onError(e.toString());
+    }
   }
 
   @Override
   public void addTokenChangeListener(TokenChangeListener listener) {
-    ImplFirebaseTrampolines.addAuthStateChangeListener(firebaseApp, wrap(listener));
+    CredentialsChangedListener listenerWrapper = new TokenChangeListenerWrapper(
+        listener, executorService, authVariable);
+    credentials.addChangeListener(listenerWrapper);
   }
 
-  @Override
-  public void removeTokenChangeListener(TokenChangeListener listener) {
-    ImplFirebaseTrampolines.removeAuthStateChangeListener(firebaseApp, wrap(listener));
-  }
+  private static String wrapOAuthToken(AccessToken result, Map<String, Object> authVariable) {
+    if (result == null) {
+      // This shouldn't happen in the actual production SDK, but can happen in tests.
+      return null;
+    }
 
-  private AuthStateListener wrap(TokenChangeListener listener) {
-    return new TokenChangeListenerWrapper(listener, firebaseApp, executorService);
+    GAuthToken googleAuthToken = new GAuthToken(result.getTokenValue(), authVariable);
+    return googleAuthToken.serializeToString();
   }
 
   /**
-   * Wraps a TokenChangeListener instance inside a FirebaseApp.AuthStateListener. Equality
+   * Wraps a TokenChangeListener instance inside a CredentialsChangedListener. Equality
    * comparisons are delegated to the TokenChangeListener so that listener addition and removal will
-   * work as expected in FirebaseApp.
+   * work as expected.
    */
-  private static class TokenChangeListenerWrapper implements AuthStateListener {
+  private static class TokenChangeListenerWrapper implements CredentialsChangedListener {
 
     private final TokenChangeListener listener;
-    private final FirebaseApp firebaseApp;
     private final ScheduledExecutorService executorService;
+    private final Map<String, Object> authVariable;
 
     TokenChangeListenerWrapper(
         TokenChangeListener listener,
-        FirebaseApp firebaseApp,
-        ScheduledExecutorService executorService) {
+        ScheduledExecutorService executorService,
+        Map<String, Object> authVariable) {
       this.listener = checkNotNull(listener, "Listener must not be null");
-      this.firebaseApp = checkNotNull(firebaseApp, "FirebaseApp must not be null");
       this.executorService = checkNotNull(executorService, "ExecutorService must not be null");
+      this.authVariable = authVariable;
     }
 
     @Override
-    public void onAuthStateChanged(final GetTokenResult tokenResult) {
+    public void onChanged(OAuth2Credentials credentials) throws IOException {
+      // When this event fires, it is guaranteed that credentials.getAccessToken() will return a
+      // valid OAuth2 token.
+      final AccessToken accessToken = credentials.getAccessToken();
+
       // Notify the TokenChangeListener on database's thread pool to make sure that
       // all database work happens on database worker threads.
       executorService.execute(
           new Runnable() {
             @Override
             public void run() {
-              listener.onTokenChange(wrapOAuthToken(firebaseApp, tokenResult));
+              listener.onTokenChange(wrapOAuthToken(accessToken, authVariable));
             }
           });
     }
diff --git a/src/main/java/com/google/firebase/database/core/Repo.java b/src/main/java/com/google/firebase/database/core/Repo.java
index d3fe657..8d3bc83 100644
--- a/src/main/java/com/google/firebase/database/core/Repo.java
+++ b/src/main/java/com/google/firebase/database/core/Repo.java
@@ -133,14 +133,6 @@ public class Repo implements PersistentConnection.Delegate {
     this.ctx.getAuthTokenProvider()
         .addTokenChangeListener(
           new AuthTokenProvider.TokenChangeListener() {
-            // TODO: Remove this once AndroidAuthTokenProvider is updated to call the
-            // other overload.
-            @Override
-            public void onTokenChange() {
-              operationLogger.debug("Auth token changed, triggering auth token refresh");
-              connection.refreshAuthToken();
-            }
-  
             @Override
             public void onTokenChange(String token) {
               operationLogger.debug("Auth token changed, triggering auth token refresh");
diff --git a/src/main/java/com/google/firebase/internal/AuthStateListener.java b/src/main/java/com/google/firebase/internal/AuthStateListener.java
deleted file mode 100644
index 859e883..0000000
--- a/src/main/java/com/google/firebase/internal/AuthStateListener.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.firebase.internal;
-
-/** An event listener for receiving authentication state change events (i.e. token renewals). */
-public interface AuthStateListener {
-
-  /**
-   * Gets called when FirebaseApp fetches a new access token. The GetTokenResult encapsulates the
-   * newly fetched token.
-   */
-  void onAuthStateChanged(GetTokenResult tokenResult);
-}
diff --git a/src/main/java/com/google/firebase/internal/FirebaseCloudCredentials.java b/src/main/java/com/google/firebase/internal/FirebaseCloudCredentials.java
deleted file mode 100644
index 5786342..0000000
--- a/src/main/java/com/google/firebase/internal/FirebaseCloudCredentials.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.firebase.internal;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.google.auth.Credentials;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.firebase.FirebaseApp;
-import com.google.firebase.ImplFirebaseTrampolines;
-import com.google.firebase.tasks.Continuation;
-import com.google.firebase.tasks.Task;
-import com.google.firebase.tasks.Tasks;
-import java.io.IOException;
-import java.net.URI;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-
-/**
- * A Google cloud credential implementation that uses OAuth2 access tokens obtained from a
- * <code>FirebaseApp</code> to authenticate cloud API calls. This essentially acts as a bridge
- * between Firebase Admin SDK APIs and the Google cloud <code>Credentials</code> API.
- */
-public final class FirebaseCloudCredentials extends Credentials {
-
-  private final FirebaseApp app;
-
-  public FirebaseCloudCredentials(FirebaseApp app) {
-    this.app = checkNotNull(app);
-  }
-
-  @Override
-  public String getAuthenticationType() {
-    return "OAuth2";
-  }
-
-  @Override
-  public Map<String, List<String>> getRequestMetadata(URI uri) throws IOException {
-    Task<String> task = ImplFirebaseTrampolines.getToken(app, false).continueWith(
-        new Continuation<GetTokenResult, String>() {
-          @Override
-          public String then(Task<GetTokenResult> task) throws Exception {
-            return task.getResult().getToken();
-          }
-        });
-    try {
-      String authHeader = "Bearer " + Tasks.await(task);
-      return ImmutableMap.<String, List<String>>of(
-          "Authorization", ImmutableList.of(authHeader));
-    } catch (ExecutionException | InterruptedException e) {
-      throw new IOException("Failed to acquire an OAuth token", e);
-    }
-  }
-
-  @Override
-  public boolean hasRequestMetadata() {
-    return true;
-  }
-
-  @Override
-  public boolean hasRequestMetadataOnly() {
-    return true;
-  }
-
-  @Override
-  public void refresh() throws IOException {
-    ImplFirebaseTrampolines.getToken(app, true);
-  }
-
-}
diff --git a/src/main/java/com/google/firebase/internal/GetTokenResult.java b/src/main/java/com/google/firebase/internal/GetTokenResult.java
deleted file mode 100644
index 4a76c7e..0000000
--- a/src/main/java/com/google/firebase/internal/GetTokenResult.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.firebase.internal;
-
-import com.google.common.base.Objects;
-
-/** This class mirrors the GetAccessTokenResult in GITKit. */
-public class GetTokenResult {
-
-  private String token;
-
-  /**
-   * @param token represents the {@link String} access token.
-   */
-  public GetTokenResult(String token) {
-    this.token = token;
-  }
-
-  @Nullable
-  public String getToken() {
-    return token;
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(token);
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    return obj != null
-        && obj instanceof GetTokenResult
-        && Objects.equal(token, ((GetTokenResult) obj).token);
-  }
-}


Test patch:
diff --git a/src/test/java/com/google/firebase/FirebaseAppTest.java b/src/test/java/com/google/firebase/FirebaseAppTest.java
index 9e79b10..b60a598 100644
--- a/src/test/java/com/google/firebase/FirebaseAppTest.java
+++ b/src/test/java/com/google/firebase/FirebaseAppTest.java
@@ -29,19 +29,15 @@ import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.OAuth2Credentials;
+import com.google.auth.oauth2.OAuth2Credentials.CredentialsChangedListener;
 import com.google.common.base.Defaults;
 import com.google.common.io.BaseEncoding;
 import com.google.firebase.FirebaseApp.TokenRefresher;
 import com.google.firebase.FirebaseOptions.Builder;
-import com.google.firebase.auth.FirebaseCredential;
-import com.google.firebase.auth.FirebaseCredentials;
-import com.google.firebase.auth.GoogleOAuthAccessToken;
 import com.google.firebase.database.FirebaseDatabase;
-import com.google.firebase.internal.AuthStateListener;
-import com.google.firebase.internal.GetTokenResult;
-import com.google.firebase.tasks.Task;
-import com.google.firebase.tasks.TaskCompletionSource;
-import com.google.firebase.tasks.Tasks;
 import com.google.firebase.testing.FirebaseAppRule;
 import com.google.firebase.testing.ServiceAccount;
 import com.google.firebase.testing.TestUtils;
@@ -52,6 +48,7 @@ import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.Callable;
@@ -65,18 +62,19 @@ import org.mockito.Mockito;
 /** 
  * Unit tests for {@link com.google.firebase.FirebaseApp}.
  */
-// TODO: uncomment lines when Firebase API targets are in integ.
 public class FirebaseAppTest {
 
   private static final FirebaseOptions OPTIONS =
       new FirebaseOptions.Builder()
-          .setCredential(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
+          .setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
           .build();
-  private static final FirebaseOptions MOCK_CREDENTIAL_OPTIONS =
-      new Builder().setCredential(new MockFirebaseCredential()).build();
 
   @Rule public FirebaseAppRule firebaseAppRule = new FirebaseAppRule();
 
+  private static FirebaseOptions getMockCredentialOptions() {
+    return new Builder().setCredentials(new MockGoogleCredentials()).build();
+  }
+
   private static void invokePublicInstanceMethodWithDefaultValues(Object instance, Method method)
       throws InvocationTargetException, IllegalAccessException {
     List<Object> parameters = new ArrayList<>(method.getParameterTypes().length);
@@ -104,6 +102,23 @@ public class FirebaseAppTest {
     FirebaseApp.getInstance(name);
   }
 
+  @Test
+  public void testGetProjectIdFromOptions() throws Exception {
+    FirebaseOptions options = new FirebaseOptions.Builder(OPTIONS)
+        .setProjectId("explicit-project-id")
+        .build();
+    FirebaseApp app = FirebaseApp.initializeApp(options, "myApp");
+    String projectId = ImplFirebaseTrampolines.getProjectId(app);
+    assertEquals("explicit-project-id", projectId);
+  }
+
+  @Test
+  public void testGetProjectIdFromCredential() throws Exception {
+    FirebaseApp app = FirebaseApp.initializeApp(OPTIONS, "myApp");
+    String projectId = ImplFirebaseTrampolines.getProjectId(app);
+    assertEquals("mock-project-id", projectId);
+  }
+
   @Test(expected = IllegalStateException.class)
   public void testRehydratingDeletedInstanceThrows() {
     final String name = "myApp";
@@ -164,7 +179,7 @@ public class FirebaseAppTest {
 
   @Test
   public void testGetNullApp() {
-    FirebaseApp app1 = FirebaseApp.initializeApp(OPTIONS, "app");
+    FirebaseApp.initializeApp(OPTIONS, "app");
     try {
       FirebaseApp.getInstance(null);
       fail("Not thrown");
@@ -177,7 +192,7 @@ public class FirebaseAppTest {
   public void testToString() throws IOException {
     FirebaseOptions options =
         new FirebaseOptions.Builder()
-            .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+            .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
             .build();
     FirebaseApp app = FirebaseApp.initializeApp(options, "app");
     String pattern = "FirebaseApp\\{name=app}";
@@ -189,18 +204,12 @@ public class FirebaseAppTest {
     // delete and hidden methods shouldn't throw even after delete.
     Collection<String> allowedToCallAfterDelete =
         Arrays.asList(
-            "addAuthStateChangeListener",
-            "addBackgroundStateChangeListener",
             "delete",
             "equals",
             "getListeners",
             "getPersistenceKey",
             "hashCode",
             "isDefaultApp",
-            "notifyAuthStateListeners",
-            "removeAuthStateChangeListener",
-            "removeBackgroundStateChangeListener",
-            "setTokenProvider",
             "toString");
     FirebaseApp firebaseApp = FirebaseApp.initializeApp(OPTIONS, "myApp");
     firebaseApp.delete();
@@ -258,11 +267,11 @@ public class FirebaseAppTest {
 
   @Test
   public void testTokenCaching() throws ExecutionException, InterruptedException, IOException {
-    FirebaseApp firebaseApp = FirebaseApp.initializeApp(MOCK_CREDENTIAL_OPTIONS, "myApp");
-    GetTokenResult token1 = TestOnlyImplFirebaseTrampolines.getToken(
-        firebaseApp, false).get();
-    GetTokenResult token2 = TestOnlyImplFirebaseTrampolines.getToken(
-        firebaseApp, false).get();
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp(getMockCredentialOptions(), "myApp");
+    String token1 = TestOnlyImplFirebaseTrampolines.getToken(
+        firebaseApp, false);
+    String token2 = TestOnlyImplFirebaseTrampolines.getToken(
+        firebaseApp, false);
     Assert.assertNotNull(token1);
     Assert.assertNotNull(token2);
     Assert.assertEquals(token1, token2);
@@ -270,125 +279,70 @@ public class FirebaseAppTest {
 
   @Test
   public void testTokenForceRefresh() throws ExecutionException, InterruptedException, IOException {
-    FirebaseApp firebaseApp = FirebaseApp.initializeApp(MOCK_CREDENTIAL_OPTIONS, "myApp");
-    GetTokenResult token1 = TestOnlyImplFirebaseTrampolines.getToken(
-        firebaseApp, false).get();
-    GetTokenResult token2 = TestOnlyImplFirebaseTrampolines.getToken(
-        firebaseApp, true).get();
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp(getMockCredentialOptions(), "myApp");
+    String token1 = TestOnlyImplFirebaseTrampolines.getToken(firebaseApp, false);
+    String token2 = TestOnlyImplFirebaseTrampolines.getToken(firebaseApp, true);
     Assert.assertNotNull(token1);
     Assert.assertNotNull(token2);
     Assert.assertNotEquals(token1, token2);
   }
 
   @Test
-  public void testTokenExpiration() throws ExecutionException, InterruptedException, IOException {
-    TestClock clock = new TestClock();
-    FirebaseApp firebaseApp = FirebaseApp.initializeApp(new Builder()
-        .setCredential(new ClockedMockFirebaseCredential(clock)).build(), "myApp",
-        FirebaseApp.DEFAULT_TOKEN_REFRESHER_FACTORY, clock);
-    GetTokenResult token1 = TestOnlyImplFirebaseTrampolines.getToken(
-        firebaseApp, false).get();
-    GetTokenResult token2 = TestOnlyImplFirebaseTrampolines.getToken(
-        firebaseApp, false).get();
-    Assert.assertNotNull(token1);
-    Assert.assertNotNull(token2);
-    Assert.assertEquals(token1, token2);
-
-    clock.timestamp += TimeUnit.HOURS.toMillis(1);
-    GetTokenResult token3 = TestOnlyImplFirebaseTrampolines.getToken(
-        firebaseApp, false).get();
-    Assert.assertNotNull(token3);
-    Assert.assertNotEquals(token1, token3);
-  }
-
-  @Test
-  public void testAddAuthStateListenerWithoutInitialToken() {
-    FirebaseApp firebaseApp = FirebaseApp.initializeApp(MOCK_CREDENTIAL_OPTIONS, "myApp");
-    AuthStateListener listener = mock(AuthStateListener.class);
-    firebaseApp.addAuthStateListener(listener);
-    verify(listener, never()).onAuthStateChanged(Mockito.any(GetTokenResult.class));
+  public void testAddCredentialsChangedListenerWithoutInitialToken() throws IOException {
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp(getMockCredentialOptions(), "myApp");
+    CredentialsChangedListener listener = mock(CredentialsChangedListener.class);
+    ImplFirebaseTrampolines.getCredentials(firebaseApp).addChangeListener(listener);
+    verify(listener, never()).onChanged(Mockito.any(OAuth2Credentials.class));
   }
 
   @Test
-  public void testAuthStateListenerAddWithInitialToken() throws Exception {
-    FirebaseApp firebaseApp = FirebaseApp.initializeApp(MOCK_CREDENTIAL_OPTIONS, "myApp");
-    Tasks.await(firebaseApp.getToken(true));
-    final TaskCompletionSource<Boolean> completionSource = new TaskCompletionSource<>();
-    AuthStateListener listener =
-        new AuthStateListener() {
-          @Override
-          public void onAuthStateChanged(GetTokenResult tokenResult) {
-            completionSource.setResult(true);
-          }
-        };
-    firebaseApp.addAuthStateListener(listener);
-    Tasks.await(completionSource.getTask());
-    assertTrue(completionSource.getTask().isSuccessful());
-  }
-
-  @Test
-  public void testAuthStateListenerOnTokenChange() throws Exception {
-    FirebaseApp firebaseApp = FirebaseApp.initializeApp(MOCK_CREDENTIAL_OPTIONS, "myApp");
-    AuthStateListener listener = mock(AuthStateListener.class);
-    firebaseApp.addAuthStateListener(listener);
+  public void testCredentialsChangedListenerOnTokenChange() throws Exception {
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp(getMockCredentialOptions(), "myApp");
+    CredentialsChangedListener listener = mock(CredentialsChangedListener.class);
+    ImplFirebaseTrampolines.getCredentials(firebaseApp).addChangeListener(listener);
 
     for (int i = 0; i < 5; i++) {
-      Tasks.await(firebaseApp.getToken(true));
-      verify(listener, times(i + 1)).onAuthStateChanged(Mockito.any(GetTokenResult.class));
+      TestOnlyImplFirebaseTrampolines.getToken(firebaseApp, true);
+      verify(listener, times(i + 1)).onChanged(Mockito.any(OAuth2Credentials.class));
     }
   }
 
   @Test
-  public void testAuthStateListenerWithNoRefresh() throws Exception {
-    FirebaseApp firebaseApp = FirebaseApp.initializeApp(MOCK_CREDENTIAL_OPTIONS, "myApp");
-    AuthStateListener listener = mock(AuthStateListener.class);
-    firebaseApp.addAuthStateListener(listener);
-
-    Tasks.await(firebaseApp.getToken(true));
-    verify(listener, times(1)).onAuthStateChanged(Mockito.any(GetTokenResult.class));
-
-    reset(listener);
-    Tasks.await(firebaseApp.getToken(false));
-    verify(listener, never()).onAuthStateChanged(Mockito.any(GetTokenResult.class));
-  }
-
-  @Test
-  public void testAuthStateListenerRemoval() throws Exception {
-    FirebaseApp firebaseApp = FirebaseApp.initializeApp(MOCK_CREDENTIAL_OPTIONS, "myApp");
-    AuthStateListener listener = mock(AuthStateListener.class);
-    firebaseApp.addAuthStateListener(listener);
+  public void testCredentialsChangedListenerWithNoRefresh() throws Exception {
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp(getMockCredentialOptions(), "myApp");
+    CredentialsChangedListener listener = mock(CredentialsChangedListener.class);
+    ImplFirebaseTrampolines.getCredentials(firebaseApp).addChangeListener(listener);
 
-    Tasks.await(firebaseApp.getToken(true));
-    verify(listener, times(1)).onAuthStateChanged(Mockito.any(GetTokenResult.class));
+    TestOnlyImplFirebaseTrampolines.getToken(firebaseApp, true);
+    verify(listener, times(1)).onChanged(Mockito.any(OAuth2Credentials.class));
 
     reset(listener);
-    firebaseApp.removeAuthStateListener(listener);
-    Tasks.await(firebaseApp.getToken(false));
-    verify(listener, never()).onAuthStateChanged(Mockito.any(GetTokenResult.class));
+    TestOnlyImplFirebaseTrampolines.getToken(firebaseApp, false);
+    verify(listener, never()).onChanged(Mockito.any(OAuth2Credentials.class));
   }
 
   @Test
   public void testProactiveTokenRefresh() throws Exception {
     MockTokenRefresherFactory factory = new MockTokenRefresherFactory();
-    FirebaseApp firebaseApp = FirebaseApp.initializeApp(MOCK_CREDENTIAL_OPTIONS, "myApp",
-        factory, FirebaseApp.DEFAULT_CLOCK);
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp(getMockCredentialOptions(), "myApp",
+        factory);
     MockTokenRefresher tokenRefresher = factory.instance;
     Assert.assertNotNull(tokenRefresher);
 
-    AuthStateListener listener = mock(AuthStateListener.class);
-    firebaseApp.addAuthStateListener(listener);
+    CredentialsChangedListener listener = mock(CredentialsChangedListener.class);
+    ImplFirebaseTrampolines.getCredentials(firebaseApp).addChangeListener(listener);
 
-    Tasks.await(firebaseApp.getToken(true));
-    verify(listener, times(1)).onAuthStateChanged(Mockito.any(GetTokenResult.class));
+    TestOnlyImplFirebaseTrampolines.getToken(firebaseApp, true);
+    verify(listener, times(1)).onChanged(Mockito.any(OAuth2Credentials.class));
 
     tokenRefresher.simulateDelay(55);
-    verify(listener, times(2)).onAuthStateChanged(Mockito.any(GetTokenResult.class));
+    verify(listener, times(2)).onChanged(Mockito.any(OAuth2Credentials.class));
 
     tokenRefresher.simulateDelay(20);
-    verify(listener, times(2)).onAuthStateChanged(Mockito.any(GetTokenResult.class));
+    verify(listener, times(2)).onChanged(Mockito.any(OAuth2Credentials.class));
 
     tokenRefresher.simulateDelay(35);
-    verify(listener, times(3)).onAuthStateChanged(Mockito.any(GetTokenResult.class));
+    verify(listener, times(3)).onChanged(Mockito.any(OAuth2Credentials.class));
   }
 
   @Test(expected = IllegalArgumentException.class)
@@ -401,33 +355,9 @@ public class FirebaseAppTest {
     new FirebaseException("");
   }
 
-
-  private static class MockFirebaseCredential implements FirebaseCredential {
-    @Override
-    public Task<GoogleOAuthAccessToken> getAccessToken() {
-      return Tasks.forResult(new GoogleOAuthAccessToken(UUID.randomUUID().toString(),
-          System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)));
-    }
-  }
-
-  private static class ClockedMockFirebaseCredential implements FirebaseCredential {
-
-    private final TestClock clock;
-
-    ClockedMockFirebaseCredential(TestClock clock) {
-      this.clock = clock;
-    }
-
-    @Override
-    public Task<GoogleOAuthAccessToken> getAccessToken() {
-      return Tasks.forResult(new GoogleOAuthAccessToken(UUID.randomUUID().toString(),
-          clock.now() + TimeUnit.HOURS.toMillis(1)));
-    }
-  }
-
   private static class MockTokenRefresher extends TokenRefresher {
 
-    private Callable<Task<GetTokenResult>> task;
+    private Callable<Void> task;
     private long executeAt;
     private long time;
 
@@ -441,7 +371,7 @@ public class FirebaseAppTest {
     }
 
     @Override
-    protected void scheduleNext(Callable<Task<GetTokenResult>> task, long delayMillis) {
+    protected void scheduleNext(Callable<Void> task, long delayMillis) {
       this.task = task;
       executeAt = time + delayMillis;
     }
@@ -453,17 +383,12 @@ public class FirebaseAppTest {
      * @param delayMinutes Duration in minutes to advance the clock by
      */
     void simulateDelay(int delayMinutes) throws Exception {
-      Task<GetTokenResult> refreshTask = null;
       synchronized (this) {
         time += TimeUnit.MINUTES.toMillis(delayMinutes);
         if (task != null && time >= executeAt) {
-          refreshTask = task.call();
+          task.call();
         }
       }
-
-      if (refreshTask != null) {
-        Tasks.await(refreshTask);
-      }
     }
   }
 
@@ -478,13 +403,13 @@ public class FirebaseAppTest {
     }
   }
 
-  private static class TestClock extends FirebaseApp.Clock {
 
-    private long timestamp;
+  private static class MockGoogleCredentials extends GoogleCredentials {
 
     @Override
-    long now() {
-      return timestamp;
+    public AccessToken refreshAccessToken() throws IOException {
+      Date expiry = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
+      return new AccessToken(UUID.randomUUID().toString(), expiry);
     }
   }
 }
diff --git a/src/test/java/com/google/firebase/FirebaseOptionsTest.java b/src/test/java/com/google/firebase/FirebaseOptionsTest.java
index 5791947..1d9bab7 100644
--- a/src/test/java/com/google/firebase/FirebaseOptionsTest.java
+++ b/src/test/java/com/google/firebase/FirebaseOptionsTest.java
@@ -22,22 +22,20 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
 
 import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
 import com.google.api.client.http.javanet.NetHttpTransport;
 import com.google.api.client.json.gson.GsonFactory;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
 import com.google.firebase.auth.FirebaseCredential;
 import com.google.firebase.auth.FirebaseCredentials;
-import com.google.firebase.auth.TestOnlyImplFirebaseAuthTrampolines;
-import com.google.firebase.database.TestHelpers;
-import com.google.firebase.tasks.OnSuccessListener;
+import com.google.firebase.auth.GoogleOAuthAccessToken;
 import com.google.firebase.tasks.Task;
-import com.google.firebase.tasks.Tasks;
 import com.google.firebase.testing.ServiceAccount;
 import com.google.firebase.testing.TestUtils;
 import java.io.IOException;
-import java.util.concurrent.Semaphore;
 import org.junit.Test;
 
 /** 
@@ -47,82 +45,101 @@ public class FirebaseOptionsTest {
 
   private static final String FIREBASE_DB_URL = "https://mock-project.firebaseio.com";
   private static final String FIREBASE_STORAGE_BUCKET = "mock-storage-bucket";
+  private static final String FIREBASE_PROJECT_ID = "explicit-project-id";
 
   private static final FirebaseOptions ALL_VALUES_OPTIONS =
       new FirebaseOptions.Builder()
           .setDatabaseUrl(FIREBASE_DB_URL)
-          .setCredential(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
+          .setStorageBucket(FIREBASE_STORAGE_BUCKET)
+          .setProjectId(FIREBASE_PROJECT_ID)
+          .setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
           .build();
 
   @Test
   public void createOptionsWithAllValuesSet() throws IOException, InterruptedException {
-    final Semaphore semaphore = new Semaphore(0);
     GsonFactory jsonFactory = new GsonFactory();
     NetHttpTransport httpTransport = new NetHttpTransport();
     FirebaseOptions firebaseOptions =
         new FirebaseOptions.Builder()
             .setDatabaseUrl(FIREBASE_DB_URL)
             .setStorageBucket(FIREBASE_STORAGE_BUCKET)
-            .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+            .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
+            .setProjectId(FIREBASE_PROJECT_ID)
             .setJsonFactory(jsonFactory)
             .setHttpTransport(httpTransport)
             .build();
     assertEquals(FIREBASE_DB_URL, firebaseOptions.getDatabaseUrl());
     assertEquals(FIREBASE_STORAGE_BUCKET, firebaseOptions.getStorageBucket());
+    assertEquals(FIREBASE_PROJECT_ID, firebaseOptions.getProjectId());
     assertSame(jsonFactory, firebaseOptions.getJsonFactory());
     assertSame(httpTransport, firebaseOptions.getHttpTransport());
-    TestOnlyImplFirebaseAuthTrampolines.getCertificate(firebaseOptions.getCredential())
-        .addOnSuccessListener(
-            new OnSuccessListener<GoogleCredential>() {
-              @Override
-              public void onSuccess(GoogleCredential googleCredential) {
-                assertEquals(
-                    ServiceAccount.EDITOR.getEmail(), googleCredential.getServiceAccountId());
-                semaphore.release();
-              }
-            });
-    TestHelpers.waitFor(semaphore);
+
+    GoogleCredentials credentials = firebaseOptions.getCredentials();
+    assertNotNull(credentials);
+    assertTrue(credentials instanceof ServiceAccountCredentials);
+    assertEquals(
+        GoogleCredential.fromStream(ServiceAccount.EDITOR.asStream()).getServiceAccountId(),
+        ((ServiceAccountCredentials) credentials).getClientEmail());
   }
 
   @Test
   public void createOptionsWithOnlyMandatoryValuesSet() throws IOException, InterruptedException {
-    final Semaphore semaphore = new Semaphore(0);
     FirebaseOptions firebaseOptions =
         new FirebaseOptions.Builder()
-            .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+            .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
             .build();
     assertNotNull(firebaseOptions.getJsonFactory());
     assertNotNull(firebaseOptions.getHttpTransport());
     assertNull(firebaseOptions.getDatabaseUrl());
     assertNull(firebaseOptions.getStorageBucket());
-    TestOnlyImplFirebaseAuthTrampolines.getCertificate(firebaseOptions.getCredential())
-        .addOnSuccessListener(
-            new OnSuccessListener<GoogleCredential>() {
-              @Override
-              public void onSuccess(GoogleCredential googleCredential) {
-                try {
-                  assertEquals(
-                      GoogleCredential.fromStream(ServiceAccount.EDITOR.asStream())
-                          .getServiceAccountId(),
-                      googleCredential.getServiceAccountId());
-                  semaphore.release();
-                } catch (IOException e) {
-                  fail();
-                }
-              }
-            });
-    TestHelpers.waitFor(semaphore);
+
+    GoogleCredentials credentials = firebaseOptions.getCredentials();
+    assertNotNull(credentials);
+    assertTrue(credentials instanceof ServiceAccountCredentials);
+    assertEquals(
+        GoogleCredential.fromStream(ServiceAccount.EDITOR.asStream()).getServiceAccountId(),
+        ((ServiceAccountCredentials) credentials).getClientEmail());
   }
 
   @Test
-  public void createOptionsWithServiceAccountSetsProjectId() throws Exception {
+  public void createOptionsWithFirebaseCredential() throws IOException {
     FirebaseOptions firebaseOptions =
         new FirebaseOptions.Builder()
             .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
             .build();
-    Task<String> projectId =
-        TestOnlyImplFirebaseAuthTrampolines.getProjectId(firebaseOptions.getCredential());
-    assertEquals("mock-project-id", Tasks.await(projectId));
+
+    assertNotNull(firebaseOptions.getJsonFactory());
+    assertNotNull(firebaseOptions.getHttpTransport());
+    assertNull(firebaseOptions.getDatabaseUrl());
+    assertNull(firebaseOptions.getStorageBucket());
+
+    GoogleCredentials credentials = firebaseOptions.getCredentials();
+    assertNotNull(credentials);
+    assertTrue(credentials instanceof ServiceAccountCredentials);
+    assertEquals(
+        GoogleCredential.fromStream(ServiceAccount.EDITOR.asStream()).getServiceAccountId(),
+        ((ServiceAccountCredentials) credentials).getClientEmail());
+  }
+
+  @Test
+  public void createOptionsWithCustomFirebaseCredential() throws IOException {
+    FirebaseOptions firebaseOptions =
+        new FirebaseOptions.Builder()
+            .setCredential(new FirebaseCredential() {
+              @Override
+              public Task<GoogleOAuthAccessToken> getAccessToken() {
+                return null;
+              }
+            })
+            .build();
+
+    assertNotNull(firebaseOptions.getJsonFactory());
+    assertNotNull(firebaseOptions.getHttpTransport());
+    assertNull(firebaseOptions.getDatabaseUrl());
+    assertNull(firebaseOptions.getStorageBucket());
+
+    GoogleCredentials credentials = firebaseOptions.getCredentials();
+    assertNotNull(credentials);
   }
 
   @Test(expected = NullPointerException.class)
@@ -134,23 +151,23 @@ public class FirebaseOptionsTest {
   public void checkToBuilderCreatesNewEquivalentInstance() {
     FirebaseOptions allValuesOptionsCopy = new FirebaseOptions.Builder(ALL_VALUES_OPTIONS).build();
     assertNotSame(ALL_VALUES_OPTIONS, allValuesOptionsCopy);
-    assertEquals(ALL_VALUES_OPTIONS.getCredential(), allValuesOptionsCopy.getCredential());
+    assertEquals(ALL_VALUES_OPTIONS.getCredentials(), allValuesOptionsCopy.getCredentials());
     assertEquals(ALL_VALUES_OPTIONS.getDatabaseUrl(), allValuesOptionsCopy.getDatabaseUrl());
+    assertEquals(ALL_VALUES_OPTIONS.getProjectId(), allValuesOptionsCopy.getProjectId());
     assertEquals(ALL_VALUES_OPTIONS.getJsonFactory(), allValuesOptionsCopy.getJsonFactory());
     assertEquals(ALL_VALUES_OPTIONS.getHttpTransport(), allValuesOptionsCopy.getHttpTransport());
   }
 
   @Test
   public void testNotEquals() throws IOException {
-    FirebaseCredential credential = FirebaseCredentials
-        .fromCertificate(ServiceAccount.EDITOR.asStream());
+    GoogleCredentials credentials = GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream());
     FirebaseOptions options1 =
         new FirebaseOptions.Builder()
-            .setCredential(credential)
+            .setCredentials(credentials)
             .build();
     FirebaseOptions options2 =
         new FirebaseOptions.Builder()
-            .setCredential(credential)
+            .setCredentials(credentials)
             .setDatabaseUrl("https://test.firebaseio.com")
             .build();
     assertFalse(options1.equals(options2));
diff --git a/src/test/java/com/google/firebase/TestOnlyImplFirebaseTrampolines.java b/src/test/java/com/google/firebase/TestOnlyImplFirebaseTrampolines.java
index 349a307..77c6be2 100644
--- a/src/test/java/com/google/firebase/TestOnlyImplFirebaseTrampolines.java
+++ b/src/test/java/com/google/firebase/TestOnlyImplFirebaseTrampolines.java
@@ -16,10 +16,9 @@
 
 package com.google.firebase;
 
-import com.google.api.core.ApiFuture;
-import com.google.firebase.internal.GetTokenResult;
-import com.google.firebase.internal.TaskToApiFuture;
-import com.google.firebase.tasks.Task;
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import java.io.IOException;
 
 /**
  * Provides trampolines into package-private APIs used by components of Firebase
@@ -38,7 +37,23 @@ public final class TestOnlyImplFirebaseTrampolines {
     FirebaseApp.clearInstancesForTest();
   }
 
-  public static ApiFuture<GetTokenResult> getToken(FirebaseApp app, boolean forceRefresh) {
-    return new TaskToApiFuture<>(app.getToken(forceRefresh));
+  public static String getToken(FirebaseApp app, boolean forceRefresh) {
+    GoogleCredentials credentials = app.getOptions().getCredentials();
+    try {
+      if (forceRefresh) {
+        credentials.refresh();
+      } else {
+        credentials.getRequestMetadata();
+      }
+
+      AccessToken token = credentials.getAccessToken();
+      return token.getTokenValue();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static GoogleCredentials getCredentials(FirebaseOptions options) {
+    return options.getCredentials();
   }
 }
diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
index ea39e4b..ba9e3b9 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
@@ -27,11 +27,19 @@ import static org.junit.Assert.fail;
 
 import com.google.api.client.googleapis.testing.auth.oauth2.MockTokenServerTransport;
 import com.google.api.client.googleapis.util.Utils;
+import com.google.api.client.http.HttpTransport;
 import com.google.api.client.json.gson.GsonFactory;
 import com.google.api.core.ApiFuture;
+import com.google.auth.http.HttpTransportFactory;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import com.google.auth.oauth2.UserCredentials;
+import com.google.common.base.Defaults;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
+import com.google.firebase.ImplFirebaseTrampolines;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
 import com.google.firebase.auth.internal.FirebaseCustomAuthToken;
 import com.google.firebase.database.MapBuilder;
@@ -42,10 +50,15 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.nio.file.Files;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
@@ -83,24 +96,34 @@ public class FirebaseAuthTest {
     return Arrays.asList(
         new Object[][] {
           {
-            new FirebaseOptions.Builder().setCredential(createCertificateCredential()).build(),
+            new FirebaseOptions.Builder().setCredentials(createCertificateCredential()).build(),
             /* isCertCredential */ true
           },
           {
-            new FirebaseOptions.Builder().setCredential(createRefreshTokenCredential()).build(),
+            new FirebaseOptions.Builder().setCredentials(createRefreshTokenCredential()).build(),
             /* isCertCredential */ false
           },
           {
             new FirebaseOptions.Builder()
-                .setCredential(createApplicationDefaultCredential())
+                .setCredentials(createApplicationDefaultCredential())
                 .build(),
             /* isCertCredential */ false
           },
+          {
+            new FirebaseOptions.Builder().setCredential(
+                createFirebaseCertificateCredential()).build(),
+            /* isCertCredential */ true
+          },
+          {
+            new FirebaseOptions.Builder().setCredential(
+                createFirebaseRefreshTokenCredential()).build(),
+            /* isCertCredential */ false
+          },
         });
   }
 
-  private static FirebaseCredential createApplicationDefaultCredential() throws IOException {
-    MockTokenServerTransport transport = new MockTokenServerTransport();
+  private static GoogleCredentials createApplicationDefaultCredential() throws IOException {
+    final MockTokenServerTransport transport = new MockTokenServerTransport();
     transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN);
 
     // Set the GOOGLE_APPLICATION_CREDENTIALS environment variable for application-default
@@ -117,13 +140,18 @@ public class FirebaseAuthTest {
     TestUtils.setEnvironmentVariables(environmentVariables);
     credentialsFile.deleteOnExit();
 
-    return FirebaseCredentials.applicationDefault(transport, Utils.getDefaultJsonFactory());
+    return GoogleCredentials.getApplicationDefault(new HttpTransportFactory() {
+      @Override
+      public HttpTransport create() {
+        return transport;
+      }
+    });
   }
 
-  private static FirebaseCredential createRefreshTokenCredential()
+  private static GoogleCredentials createRefreshTokenCredential()
       throws JSONException, IOException {
 
-    MockTokenServerTransport transport = new MockTokenServerTransport();
+    final MockTokenServerTransport transport = new MockTokenServerTransport();
     transport.addClient(CLIENT_ID, CLIENT_SECRET);
     transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
 
@@ -135,16 +163,50 @@ public class FirebaseAuthTest {
     InputStream refreshTokenStream =
         new ByteArrayInputStream(secretJson.toString(0).getBytes("UTF-8"));
 
-    return FirebaseCredentials.fromRefreshToken(
-        refreshTokenStream, transport, Utils.getDefaultJsonFactory());
+    return UserCredentials.fromStream(refreshTokenStream, new HttpTransportFactory() {
+      @Override
+      public HttpTransport create() {
+        return transport;
+      }
+    });
   }
 
-  private static FirebaseCredential createCertificateCredential() throws IOException {
-    MockTokenServerTransport transport = new MockTokenServerTransport();
+  private static FirebaseCredential createFirebaseRefreshTokenCredential()
+      throws JSONException, IOException {
+
+    final MockTokenServerTransport transport = new MockTokenServerTransport();
+    transport.addClient(CLIENT_ID, CLIENT_SECRET);
+    transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
+
+    JSONObject secretJson = new JSONObject();
+    secretJson.put("client_id", CLIENT_ID);
+    secretJson.put("client_secret", CLIENT_SECRET);
+    secretJson.put("refresh_token", REFRESH_TOKEN);
+    secretJson.put("type", "authorized_user");
+    InputStream refreshTokenStream =
+        new ByteArrayInputStream(secretJson.toString(0).getBytes("UTF-8"));
+
+    return FirebaseCredentials.fromRefreshToken(refreshTokenStream, transport,
+        Utils.getDefaultJsonFactory());
+  }
+
+  private static GoogleCredentials createCertificateCredential() throws IOException {
+    final MockTokenServerTransport transport = new MockTokenServerTransport();
     transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN);
+    return ServiceAccountCredentials.fromStream(ServiceAccount.EDITOR.asStream(),
+        new HttpTransportFactory() {
+          @Override
+          public HttpTransport create() {
+            return transport;
+          }
+        });
+  }
 
-    return FirebaseCredentials.fromCertificate(
-        ServiceAccount.EDITOR.asStream(), transport, Utils.getDefaultJsonFactory());
+  private static FirebaseCredential createFirebaseCertificateCredential() throws IOException {
+    final MockTokenServerTransport transport = new MockTokenServerTransport();
+    transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN);
+    return FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream(),
+        transport, Utils.getDefaultJsonFactory());
   }
 
   @Before
@@ -163,9 +225,7 @@ public class FirebaseAuthTest {
     FirebaseAuth defaultAuth = FirebaseAuth.getInstance();
     assertNotNull(defaultAuth);
     assertSame(defaultAuth, FirebaseAuth.getInstance());
-    String token =
-        TestOnlyImplFirebaseTrampolines.getToken(FirebaseApp.getInstance(), false).get()
-            .getToken();
+    String token = TestOnlyImplFirebaseTrampolines.getToken(FirebaseApp.getInstance(), false);
     Assert.assertTrue(!token.isEmpty());
   }
 
@@ -175,7 +235,7 @@ public class FirebaseAuthTest {
     FirebaseAuth auth = FirebaseAuth.getInstance(app);
     assertNotNull(auth);
     assertSame(auth, FirebaseAuth.getInstance(app));
-    String token = TestOnlyImplFirebaseTrampolines.getToken(app, false).get().getToken();
+    String token = TestOnlyImplFirebaseTrampolines.getToken(app, false);
     Assert.assertTrue(!token.isEmpty());
   }
 
@@ -194,18 +254,32 @@ public class FirebaseAuthTest {
   }
 
   @Test
-  public void testInvokeAfterAppDelete() throws ExecutionException, InterruptedException {
-    Assume.assumeTrue("Skipping testInvokeAfterAppDelete for non-cert credential",
-        isCertCredential);
+  public void testInvokeAfterAppDelete() throws Exception {
     FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testInvokeAfterAppDelete");
     FirebaseAuth auth = FirebaseAuth.getInstance(app);
     assertNotNull(auth);
     app.delete();
-    try {
-      auth.createCustomToken("foo");
-      fail("No error thrown when invoking auth after deleting app");
-    } catch (IllegalStateException expected) {
-      // ignore
+
+    for (Method method : auth.getClass().getDeclaredMethods()) {
+      int modifiers = method.getModifiers();
+      if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)) {
+        continue;
+      }
+
+      List<Object> parameters = new ArrayList<>(method.getParameterTypes().length);
+      for (Class<?> parameterType : method.getParameterTypes()) {
+        parameters.add(Defaults.defaultValue(parameterType));
+      }
+      try {
+        method.invoke(auth, parameters.toArray());
+        fail("No error thrown when invoking auth after deleting app");
+      } catch (InvocationTargetException expected) {
+        String message = "FirebaseAuth instance is no longer alive. This happens when "
+            + "the parent FirebaseApp instance has been deleted.";
+        Throwable cause = expected.getCause();
+        assertTrue(cause instanceof IllegalStateException);
+        assertEquals(message, cause.getMessage());
+      }
     }
   }
 
@@ -238,14 +312,15 @@ public class FirebaseAuthTest {
             .build();
     FirebaseApp app = FirebaseApp.initializeApp(options, "testGetAppWithUid");
     assertEquals("uid1", app.getOptions().getDatabaseAuthVariableOverride().get("uid"));
-    String token = TestOnlyImplFirebaseTrampolines.getToken(app, false).get().getToken();
+    String token = TestOnlyImplFirebaseTrampolines.getToken(app, false);
     Assert.assertTrue(!token.isEmpty());
   }
 
   @Test
   public void testCreateCustomToken() throws Exception {
-    Assume.assumeTrue("Skipping testCreateCustomToken for non-cert credential",
-        isCertCredential);
+    GoogleCredentials credentials = TestOnlyImplFirebaseTrampolines.getCredentials(firebaseOptions);
+    Assume.assumeTrue("Skipping testCredentialCertificateRequired for cert credential",
+        credentials instanceof ServiceAccountCredentials);
 
     FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testCreateCustomToken");
     FirebaseAuth auth = FirebaseAuth.getInstance(app);
@@ -262,8 +337,9 @@ public class FirebaseAuthTest {
 
   @Test
   public void testCreateCustomTokenWithDeveloperClaims() throws Exception {
-    Assume.assumeTrue("Skipping testCreateCustomTokenWithDeveloperClaims for non-cert credential",
-        isCertCredential);
+    GoogleCredentials credentials = TestOnlyImplFirebaseTrampolines.getCredentials(firebaseOptions);
+    Assume.assumeTrue("Skipping testCredentialCertificateRequired for cert credential",
+        credentials instanceof ServiceAccountCredentials);
 
     FirebaseApp app =
         FirebaseApp.initializeApp(firebaseOptions, "testCreateCustomTokenWithDeveloperClaims");
@@ -281,44 +357,62 @@ public class FirebaseAuthTest {
     assertTrue(ServiceAccount.EDITOR.verifySignature(parsedToken));
   }
 
-  @Test(expected = Exception.class)
-  public void testServiceAccountUsedAsRefreshToken() throws Exception {
-    FirebaseOptions options =
-        new FirebaseOptions.Builder()
-            .setCredential(FirebaseCredentials.fromRefreshToken(ServiceAccount.EDITOR.asStream()))
-            .build();
-    FirebaseApp app = FirebaseApp.initializeApp(options, "testCreateCustomToken");
-    Assert.assertNotNull(
-        TestOnlyImplFirebaseTrampolines.getToken(app, false).get().getToken());
-  }
-
   @Test
-  public void testCredentialCertificateRequired() throws Exception {
-    Assume.assumeFalse("Skipping testCredentialCertificateRequired for cert credential",
-        isCertCredential);
+  public void testServiceAccountRequired() throws Exception {
+    GoogleCredentials credentials = TestOnlyImplFirebaseTrampolines.getCredentials(firebaseOptions);
+    Assume.assumeFalse("Skipping testServiceAccountRequired for service account credentials",
+        credentials instanceof ServiceAccountCredentials);
 
+    FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testServiceAccountRequired");
+    try {
+      FirebaseAuth.getInstance(app).createCustomTokenAsync("foo").get();
+      fail("Expected exception.");
+    } catch (IllegalStateException expected) {
+      Assert.assertEquals(
+          "Must initialize FirebaseApp with a service account credential to call "
+              + "createCustomToken()",
+          expected.getMessage());
+    }
+  }
 
-    FirebaseApp app =
-        FirebaseApp.initializeApp(firebaseOptions, "testCredentialCertificateRequired");
+  @Test
+  public void testProjectIdRequired() throws Exception {
+    FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testProjectIdRequired");
+    String projectId = ImplFirebaseTrampolines.getProjectId(app);
+    Assume.assumeTrue("Skipping testProjectIdRequired for settings with project ID",
+        Strings.isNullOrEmpty(projectId));
 
     try {
       FirebaseAuth.getInstance(app).verifyIdTokenAsync("foo").get();
       fail("Expected exception.");
-    } catch (Exception expected) {
+    } catch (IllegalStateException expected) {
       Assert.assertEquals(
-          "com.google.firebase.FirebaseException: Must initialize FirebaseApp with a certificate "
-              + "credential to call verifyIdToken()",
+          "Must initialize FirebaseApp with a project ID to call verifyIdToken()",
           expected.getMessage());
     }
+  }
+
+  @Test
+  public void testVerifyIdTokenWithExplicitProjectId() throws Exception {
+    GoogleCredentials credentials = TestOnlyImplFirebaseTrampolines.getCredentials(firebaseOptions);
+    Assume.assumeFalse(
+        "Skipping testVerifyIdTokenWithExplicitProjectId for service account credentials",
+        credentials instanceof ServiceAccountCredentials);
 
+    FirebaseOptions options =
+        new FirebaseOptions.Builder(firebaseOptions)
+            .setProjectId("mock-project-id")
+            .build();
+    FirebaseApp app = FirebaseApp.initializeApp(options, "testVerifyIdTokenWithExplicitProjectId");
     try {
-      FirebaseAuth.getInstance(app).createCustomTokenAsync("foo").get();
+      FirebaseAuth.getInstance(app).verifyIdTokenAsync("foo").get();
       fail("Expected exception.");
-    } catch (Exception expected) {
-      Assert.assertEquals(
-          "com.google.firebase.FirebaseException: Must initialize FirebaseApp with a certificate "
-              + "credential to call createCustomToken()",
+    } catch (ExecutionException expected) {
+      Assert.assertNotEquals(
+          "com.google.firebase.FirebaseException: Must initialize FirebaseApp with a project ID "
+              + "to call verifyIdToken()",
           expected.getMessage());
+      assertTrue(expected.getCause() instanceof IllegalArgumentException);
     }
   }
 
diff --git a/src/test/java/com/google/firebase/auth/FirebaseCredentialsTest.java b/src/test/java/com/google/firebase/auth/FirebaseCredentialsTest.java
index d4b7e3a..ed2b9da 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseCredentialsTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseCredentialsTest.java
@@ -18,13 +18,14 @@ package com.google.firebase.auth;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
-import com.google.api.client.googleapis.testing.auth.oauth2.MockGoogleCredential;
-import com.google.api.client.googleapis.testing.auth.oauth2.MockGoogleCredential.Builder;
 import com.google.api.client.googleapis.testing.auth.oauth2.MockTokenServerTransport;
 import com.google.api.client.googleapis.util.Utils;
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.common.collect.ImmutableMap;
-import com.google.firebase.auth.FirebaseCredentials.BaseCredential;
+import com.google.firebase.auth.internal.BaseCredential;
+import com.google.firebase.auth.internal.FirebaseCredentialsAdapter;
+import com.google.firebase.tasks.Task;
 import com.google.firebase.tasks.Tasks;
 import com.google.firebase.testing.ServiceAccount;
 import com.google.firebase.testing.TestUtils;
@@ -35,6 +36,7 @@ import java.io.InputStream;
 import java.io.PrintWriter;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
+import java.util.Date;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -66,12 +68,6 @@ public class FirebaseCredentialsTest {
     FirebaseCredentials.fromRefreshToken(null);
   }
 
-  @Test
-  public void defaultCredentialIsCached() {
-    Assert.assertEquals(
-        FirebaseCredentials.applicationDefault(), FirebaseCredentials.applicationDefault());
-  }
-
   @Test
   public void defaultCredentialDoesntRefetch() throws Exception {
     MockTokenServerTransport transport = new MockTokenServerTransport();
@@ -87,20 +83,19 @@ public class FirebaseCredentialsTest {
             .build();
     TestUtils.setEnvironmentVariables(environmentVariables);
 
-    GoogleOAuthAccessToken token =
-        Tasks.await(
-            FirebaseCredentials.applicationDefault(transport, Utils.getDefaultJsonFactory())
-                .getAccessToken());
+    FirebaseCredential credential = FirebaseCredentials.applicationDefault(
+        transport, Utils.getDefaultJsonFactory());
+    GoogleOAuthAccessToken token = Tasks.await(credential.getAccessToken());
     Assert.assertEquals(ACCESS_TOKEN, token.getAccessToken());
+    Assert.assertNotNull(((BaseCredential) credential).getGoogleCredentials());
 
     // We should still be able to fetch the token since the certificate is cached
     Assert.assertTrue(credentialsFile.delete());
-    token =
-        Tasks.await(
-            FirebaseCredentials.applicationDefault(transport, Utils.getDefaultJsonFactory())
-                .getAccessToken());
+    credential = FirebaseCredentials.applicationDefault(transport, Utils.getDefaultJsonFactory());
+    token = Tasks.await(credential.getAccessToken());
     Assert.assertNotNull(token);
     Assert.assertEquals(ACCESS_TOKEN, token.getAccessToken());
+    Assert.assertNotNull(((BaseCredential) credential).getGoogleCredentials());
   }
 
   @Test
@@ -133,12 +128,13 @@ public class FirebaseCredentialsTest {
     Assert.assertEquals(0, inputStream.available());
     inputStream.close();
 
-    Tasks.await(credential.getAccessToken());
+    Assert.assertNotNull(((BaseCredential) credential).getGoogleCredentials());
+    Assert.assertEquals(ACCESS_TOKEN, Tasks.await(credential.getAccessToken()).getAccessToken());
   }
 
   @Test
   public void certificateReadChecksForProjectId()
-      throws ExecutionException, InterruptedException {
+      throws ExecutionException, InterruptedException, IOException {
     MockTokenServerTransport transport = new MockTokenServerTransport();
     transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN);
 
@@ -150,16 +146,14 @@ public class FirebaseCredentialsTest {
     try {
       FirebaseCredentials.fromCertificate(inputStream, transport, Utils.getDefaultJsonFactory());
       Assert.fail();
-    } catch (IOException e) {
+    } catch (IllegalArgumentException e) {
       Assert.assertEquals(
-          "Failed to parse service account: 'project_id' must be set",
-          e.getMessage());
-      Assert.assertTrue(e.getCause() instanceof JSONException);
+          "Failed to parse service account: 'project_id' must be set", e.getMessage());
     }
   }
 
   @Test
-  public void certificateReadThrowsRuntimeException()
+  public void certificateReadThrowsIOException()
       throws ExecutionException, InterruptedException {
     MockTokenServerTransport transport = new MockTokenServerTransport();
     transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN);
@@ -219,11 +213,12 @@ public class FirebaseCredentialsTest {
     Assert.assertEquals(0, inputStream.available());
     inputStream.close();
 
-    Tasks.await(credential.getAccessToken());
+    Assert.assertNotNull(((BaseCredential) credential).getGoogleCredentials());
+    Assert.assertEquals(ACCESS_TOKEN, Tasks.await(credential.getAccessToken()).getAccessToken());
   }
 
   @Test
-  public void refreshTokenReadThrowsRuntimeException()
+  public void refreshTokenReadThrowsIOException()
       throws ExecutionException, InterruptedException {
     MockTokenServerTransport transport = new MockTokenServerTransport();
     transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN);
@@ -236,7 +231,6 @@ public class FirebaseCredentialsTest {
           }
         };
 
-
     try {
       FirebaseCredentials.fromRefreshToken(inputStream, transport, Utils.getDefaultJsonFactory());
       Assert.fail();
@@ -245,12 +239,14 @@ public class FirebaseCredentialsTest {
     }
   }
 
+  @Test(expected = Exception.class)
+  public void serviceAccountUsedAsRefreshToken() throws Exception {
+    FirebaseCredentials.fromRefreshToken(ServiceAccount.EDITOR.asStream());
+  }
+
   @Test
   public void tokenNotCached() throws Exception {
-    final GoogleCredential googleCredential = new MockGoogleCredential(new Builder());
-    googleCredential.setAccessToken(ACCESS_TOKEN);
-    googleCredential.setExpirationTimeMilliseconds(10L);
-    TestCredential credential = new TestCredential(googleCredential);
+    TestCredential credential = new TestCredential(new MockGoogleCredentials(ACCESS_TOKEN, 10L));
 
     for (long i = 0; i < 10; i++) {
       Assert.assertEquals(ACCESS_TOKEN, Tasks.await(credential.getAccessToken()).getAccessToken());
@@ -260,41 +256,45 @@ public class FirebaseCredentialsTest {
 
   @Test
   public void testTokenExpiration() throws Exception {
-    final GoogleCredential googleCredential = new MockGoogleCredential(new Builder());
-    googleCredential.setAccessToken(ACCESS_TOKEN);
-    TestCredential credential = new TestCredential(googleCredential);
+    final MockGoogleCredentials googleCredentials = new MockGoogleCredentials(ACCESS_TOKEN);
+    TestCredential credential = new TestCredential(googleCredentials);
 
     for (long i = 0; i < 10; i++) {
       long expiryTime = (i + 1) * 10L;
-      googleCredential.setExpirationTimeMilliseconds(expiryTime);
+      googleCredentials.setExpiryTime(expiryTime);
       GoogleOAuthAccessToken googleToken = Tasks.await(credential.getAccessToken());
       Assert.assertEquals(ACCESS_TOKEN, googleToken.getAccessToken());
       Assert.assertEquals(expiryTime, googleToken.getExpiryTime());
     }
   }
 
+  @Test
+  public void testCustomFirebaseCredential() throws IOException {
+    final Date date = new Date();
+    FirebaseCredential credential = new FirebaseCredential() {
+      @Override
+      public Task<GoogleOAuthAccessToken> getAccessToken() {
+        return Tasks.forResult(new GoogleOAuthAccessToken("token", date.getTime()));
+      }
+    };
+    GoogleCredentials googleCredentials = new FirebaseCredentialsAdapter(credential);
+    AccessToken accessToken = googleCredentials.refreshAccessToken();
+    Assert.assertEquals("token", accessToken.getTokenValue());
+    Assert.assertEquals(date, accessToken.getExpirationTime());
+  }
+
   private static class TestCredential extends BaseCredential {
 
     private final AtomicInteger fetchCount = new AtomicInteger(0);
-    private final GoogleCredential googleCredential;
 
-    TestCredential(GoogleCredential googleCredential) {
-      super(new MockTokenServerTransport(), Utils.getDefaultJsonFactory());
-      this.googleCredential = googleCredential;
+    TestCredential(GoogleCredentials googleCredentials) {
+      super(googleCredentials);
     }
 
     @Override
-    GoogleCredential fetchCredential() throws IOException {
-      return googleCredential;
-    }
-
-    @Override
-    GoogleOAuthAccessToken fetchToken(GoogleCredential credential) throws IOException {
-      try {
-        return FirebaseCredentials.newAccessToken(credential);
-      } finally {
-        fetchCount.incrementAndGet();
-      }
+    public Task<GoogleOAuthAccessToken> getAccessToken() {
+      fetchCount.incrementAndGet();
+      return super.getAccessToken();
     }
 
     int getFetchCount() {
diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
index 894a0f9..42ee62c 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
@@ -28,6 +28,7 @@ import com.google.api.client.http.HttpResponseInterceptor;
 import com.google.api.client.json.gson.GsonFactory;
 import com.google.api.client.testing.http.MockHttpTransport;
 import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.common.collect.ImmutableList;
 import com.google.firebase.auth.UserRecord.CreateRequest;
 import com.google.firebase.auth.UserRecord.UpdateRequest;
@@ -42,6 +43,7 @@ public class FirebaseUserManagerTest {
 
   private static final String TEST_TOKEN = "token";
   private static final GsonFactory gson = new GsonFactory();
+  private static final GoogleCredentials credentials = new MockGoogleCredentials(TEST_TOKEN);
 
   @Test
   public void testGetUser() throws Exception {
@@ -50,10 +52,10 @@ public class FirebaseUserManagerTest {
     MockHttpTransport transport = new MockHttpTransport.Builder()
         .setLowLevelHttpResponse(response)
         .build();
-    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
+    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
     TestResponseInterceptor interceptor = new TestResponseInterceptor();
     userManager.setInterceptor(interceptor);
-    UserRecord userRecord = userManager.getUserById("testuser", TEST_TOKEN);
+    UserRecord userRecord = userManager.getUserById("testuser");
     checkUserRecord(userRecord);
     checkRequestHeaders(interceptor);
   }
@@ -65,9 +67,9 @@ public class FirebaseUserManagerTest {
     MockHttpTransport transport = new MockHttpTransport.Builder()
         .setLowLevelHttpResponse(response)
         .build();
-    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
+    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
     try {
-      userManager.getUserById("testuser", TEST_TOKEN);
+      userManager.getUserById("testuser");
       fail("No error thrown for invalid response");
     } catch (FirebaseAuthException e) {
       assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR, e.getErrorCode());
@@ -81,10 +83,10 @@ public class FirebaseUserManagerTest {
     MockHttpTransport transport = new MockHttpTransport.Builder()
         .setLowLevelHttpResponse(response)
         .build();
-    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
+    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
     TestResponseInterceptor interceptor = new TestResponseInterceptor();
     userManager.setInterceptor(interceptor);
-    UserRecord userRecord = userManager.getUserByEmail("testuser@example.com", TEST_TOKEN);
+    UserRecord userRecord = userManager.getUserByEmail("testuser@example.com");
     checkUserRecord(userRecord);
     checkRequestHeaders(interceptor);
   }
@@ -96,10 +98,10 @@ public class FirebaseUserManagerTest {
     MockHttpTransport transport = new MockHttpTransport.Builder()
         .setLowLevelHttpResponse(response)
         .build();
-    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
+    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
     TestResponseInterceptor interceptor = new TestResponseInterceptor();
     userManager.setInterceptor(interceptor);
-    UserRecord userRecord = userManager.getUserByPhoneNumber("+1234567890", TEST_TOKEN);
+    UserRecord userRecord = userManager.getUserByPhoneNumber("+1234567890");
     checkUserRecord(userRecord);
     checkRequestHeaders(interceptor);
   }
@@ -111,10 +113,10 @@ public class FirebaseUserManagerTest {
     MockHttpTransport transport = new MockHttpTransport.Builder()
         .setLowLevelHttpResponse(response)
         .build();
-    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
+    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
     TestResponseInterceptor interceptor = new TestResponseInterceptor();
     userManager.setInterceptor(interceptor);
-    String uid = userManager.createUser(new CreateRequest(), TEST_TOKEN);
+    String uid = userManager.createUser(new CreateRequest());
     assertEquals("testuser", uid);
     checkRequestHeaders(interceptor);
   }
@@ -126,11 +128,11 @@ public class FirebaseUserManagerTest {
     MockHttpTransport transport = new MockHttpTransport.Builder()
         .setLowLevelHttpResponse(response)
         .build();
-    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
+    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
     TestResponseInterceptor interceptor = new TestResponseInterceptor();
     userManager.setInterceptor(interceptor);
     // should not throw
-    userManager.updateUser(new UpdateRequest("testuser"), TEST_TOKEN);
+    userManager.updateUser(new UpdateRequest("testuser"));
     checkRequestHeaders(interceptor);
   }
 
@@ -141,11 +143,11 @@ public class FirebaseUserManagerTest {
     MockHttpTransport transport = new MockHttpTransport.Builder()
         .setLowLevelHttpResponse(response)
         .build();
-    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
+    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
     TestResponseInterceptor interceptor = new TestResponseInterceptor();
     userManager.setInterceptor(interceptor);
     // should not throw
-    userManager.deleteUser("testuser", TEST_TOKEN);
+    userManager.deleteUser("testuser");
     checkRequestHeaders(interceptor);
   }
 
@@ -158,9 +160,9 @@ public class FirebaseUserManagerTest {
       MockHttpTransport transport = new MockHttpTransport.Builder()
           .setLowLevelHttpResponse(response)
           .build();
-      FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
+      FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
       try {
-        userManager.getUserById("testuser", "token");
+        userManager.getUserById("testuser");
         fail("No error thrown for HTTP error");
       }  catch (FirebaseAuthException e) {
         assertTrue(e.getCause() instanceof IOException);
@@ -176,9 +178,9 @@ public class FirebaseUserManagerTest {
     MockHttpTransport transport = new MockHttpTransport.Builder()
         .setLowLevelHttpResponse(response)
         .build();
-    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
+    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
     try {
-      userManager.getUserById("testuser", "token");
+      userManager.getUserById("testuser");
       fail("No error thrown for JSON error");
     }  catch (FirebaseAuthException e) {
       assertTrue(e.getCause() instanceof IOException);
diff --git a/src/test/java/com/google/firebase/auth/MockGoogleCredentials.java b/src/test/java/com/google/firebase/auth/MockGoogleCredentials.java
new file mode 100644
index 0000000..5e1bc84
--- /dev/null
+++ b/src/test/java/com/google/firebase/auth/MockGoogleCredentials.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.auth;
+
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import java.io.IOException;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A flexible GoogleCredentials implementation fortesting purposes.
+ */
+public class MockGoogleCredentials extends GoogleCredentials {
+
+  private String tokenValue;
+  private long expiryTime;
+
+  public MockGoogleCredentials() {
+    this(null);
+  }
+
+  public MockGoogleCredentials(String tokenValue) {
+    this(tokenValue, System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
+  }
+
+  public MockGoogleCredentials(String tokenValue, long expiryTime) {
+    this.tokenValue = tokenValue;
+    this.expiryTime = expiryTime;
+  }
+
+  @Override
+  public AccessToken refreshAccessToken() throws IOException {
+    return new AccessToken(tokenValue, new Date(expiryTime));
+  }
+
+  public void setExpiryTime(long expiryTime) {
+    this.expiryTime = expiryTime;
+  }
+}
diff --git a/src/test/java/com/google/firebase/auth/TestOnlyImplFirebaseAuthTrampolines.java b/src/test/java/com/google/firebase/auth/TestOnlyImplFirebaseAuthTrampolines.java
index 374161e..fb66500 100644
--- a/src/test/java/com/google/firebase/auth/TestOnlyImplFirebaseAuthTrampolines.java
+++ b/src/test/java/com/google/firebase/auth/TestOnlyImplFirebaseAuthTrampolines.java
@@ -16,14 +16,7 @@
 
 package com.google.firebase.auth;
 
-import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
-import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
 import com.google.api.client.json.JsonFactory;
-import com.google.api.client.util.Clock;
-import com.google.firebase.FirebaseApp;
-import com.google.firebase.FirebaseException;
-import com.google.firebase.tasks.Task;
-import com.google.firebase.tasks.Tasks;
 
 import java.io.IOException;
 
@@ -47,23 +40,4 @@ public final class TestOnlyImplFirebaseAuthTrampolines {
     return FirebaseToken.parse(jsonFactory, tokenString);
   }
 
-  /* FirebaseCredentials */
-  public static Task<GoogleCredential> getCertificate(FirebaseCredential credential) {
-    if (credential instanceof FirebaseCredentials.CertCredential) {
-      return ((FirebaseCredentials.CertCredential) credential).getCertificate();
-    } else {
-      return Tasks.forException(new FirebaseException("Cannot convert to CertCredential"));
-    }
-  }
-
-  /* FirebaseCredentials */
-  public static Task<String> getProjectId(FirebaseCredential credential) {
-    return ((FirebaseCredentials.CertCredential) credential).getProjectId();
-  }
-
-  /* FirebaseAuth */
-  public static FirebaseAuth getFirebaseAuthInstance(
-      FirebaseApp firebaseApp, GooglePublicKeysManager googlePublicKeysManager, Clock clock) {
-    return new FirebaseAuth(firebaseApp, googlePublicKeysManager, clock);
-  }
 }
diff --git a/src/test/java/com/google/firebase/cloud/StorageClientIT.java b/src/test/java/com/google/firebase/cloud/StorageClientIT.java
index 400fe26..57fe58b 100644
--- a/src/test/java/com/google/firebase/cloud/StorageClientIT.java
+++ b/src/test/java/com/google/firebase/cloud/StorageClientIT.java
@@ -23,7 +23,13 @@ import static org.junit.Assert.fail;
 
 import com.google.cloud.storage.Blob;
 import com.google.cloud.storage.Bucket;
+import com.google.common.io.CharStreams;
 import com.google.firebase.testing.IntegrationTestUtils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.concurrent.TimeUnit;
 import org.junit.Test;
 
 public class StorageClientIT {
@@ -51,11 +57,23 @@ public class StorageClientIT {
     }
   }
 
+  @Test
+  public void testCloudStorageSignUrl() throws IOException {
+    StorageClient storage = StorageClient.getInstance(IntegrationTestUtils.ensureDefaultApp());
+    Bucket bucket = storage.bucket();
+    Blob blob = createTextBlob(bucket, "Signed URL Test");
+    URL url = blob.signUrl(3600, TimeUnit.SECONDS);
+    try (InputStream in = url.openStream()) {
+      String result = CharStreams.toString(new InputStreamReader(in));
+      assertEquals("Signed URL Test", result);
+    } finally {
+      blob.delete();
+    }
+  }
+
   private void testBucket(Bucket bucket) {
     assertEquals(IntegrationTestUtils.getStorageBucket(), bucket.getName());
-
-    String fileName = "data_" + System.currentTimeMillis() + ".txt";
-    bucket.create(fileName, "Hello World".getBytes(), "text/plain");
+    String fileName = createTextBlob(bucket, "Hello World").getName();
 
     Blob blob = bucket.get(fileName);
     byte[] content = blob.getContent();
@@ -65,4 +83,9 @@ public class StorageClientIT {
     assertNull(bucket.get(fileName));
   }
 
+  private Blob createTextBlob(Bucket bucket, String contents) {
+    String fileName = "data_" + System.currentTimeMillis() + ".txt";
+    return bucket.create(fileName, contents.getBytes(), "text/plain");
+  }
+
 }
diff --git a/src/test/java/com/google/firebase/cloud/StorageClientTest.java b/src/test/java/com/google/firebase/cloud/StorageClientTest.java
index 3fa1663..d7b3219 100644
--- a/src/test/java/com/google/firebase/cloud/StorageClientTest.java
+++ b/src/test/java/com/google/firebase/cloud/StorageClientTest.java
@@ -19,10 +19,10 @@ package com.google.firebase.cloud;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
-import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.testing.ServiceAccount;
 import java.io.IOException;
 import org.junit.After;
@@ -38,7 +38,7 @@ public class StorageClientTest {
   @Test
   public void testInvalidConfiguration() throws IOException {
     FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
-        .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+        .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
         .build());
     try {
       StorageClient.getInstance(app).bucket();
@@ -51,7 +51,7 @@ public class StorageClientTest {
   @Test
   public void testInvalidBucketName() throws IOException {
     FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
-        .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+        .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
         .setStorageBucket("mock-bucket-name")
         .build());
     try {
@@ -72,7 +72,7 @@ public class StorageClientTest {
   @Test
   public void testAppDelete() throws IOException {
     FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
-        .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+        .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
         .setStorageBucket("mock-bucket-name")
         .build());
 
diff --git a/src/test/java/com/google/firebase/database/DataSnapshotTest.java b/src/test/java/com/google/firebase/database/DataSnapshotTest.java
index 9816184..f9208da 100644
--- a/src/test/java/com/google/firebase/database/DataSnapshotTest.java
+++ b/src/test/java/com/google/firebase/database/DataSnapshotTest.java
@@ -20,10 +20,10 @@ import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
-import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.database.core.DatabaseConfig;
 import com.google.firebase.database.snapshot.IndexedNode;
 import com.google.firebase.database.snapshot.Node;
@@ -45,7 +45,7 @@ public class DataSnapshotTest {
   public static void setUpClass() throws IOException {
     testApp = FirebaseApp.initializeApp(
         new FirebaseOptions.Builder()
-            .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+            .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
             .setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
             .build());
     // Obtain a new DatabaseConfig instance for testing. Since we are not connecting to an
diff --git a/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java b/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java
index fc7bdd9..44a72c3 100644
--- a/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java
+++ b/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java
@@ -24,11 +24,9 @@ import static org.junit.Assert.fail;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
-import com.google.firebase.auth.FirebaseCredential;
-import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.testing.ServiceAccount;
 
-import java.io.IOException;
+import com.google.firebase.testing.TestUtils;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 
@@ -39,7 +37,7 @@ public class FirebaseDatabaseTest {
   
   private static FirebaseOptions firebaseOptions =
       new FirebaseOptions.Builder()
-          .setCredential(createCertificateCredential())
+          .setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
           .setDatabaseUrl("https://firebase-db-test.firebaseio.com")
           .build();
 
@@ -48,14 +46,6 @@ public class FirebaseDatabaseTest {
     TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
   }
 
-  private static FirebaseCredential createCertificateCredential() {
-    try {
-      return FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream());
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
   @Test
   public void testGetInstance() throws ExecutionException, InterruptedException {
     FirebaseApp.initializeApp(firebaseOptions);
diff --git a/src/test/java/com/google/firebase/database/TestTokenProvider.java b/src/test/java/com/google/firebase/database/TestTokenProvider.java
index 4357532..8c1830e 100644
--- a/src/test/java/com/google/firebase/database/TestTokenProvider.java
+++ b/src/test/java/com/google/firebase/database/TestTokenProvider.java
@@ -34,14 +34,14 @@ public class TestTokenProvider implements AuthTokenProvider {
     this.executorService = executorService;
   }
 
-  public void setToken(String token) {
+  public void setToken(final String token) {
     this.token = token;
     for (final TokenChangeListener listener : this.listeners) {
       this.executorService.execute(
           new Runnable() {
             @Override
             public void run() {
-              listener.onTokenChange();
+              listener.onTokenChange(token);
             }
           });
     }
@@ -76,9 +76,4 @@ public class TestTokenProvider implements AuthTokenProvider {
   public void addTokenChangeListener(TokenChangeListener listener) {
     this.listeners.add(listener);
   }
-
-  @Override
-  public void removeTokenChangeListener(TokenChangeListener listener) {
-    this.listeners.remove(listener);
-  }
 }
diff --git a/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java b/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java
index e814cd4..791da4f 100644
--- a/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java
+++ b/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java
@@ -18,7 +18,12 @@ package com.google.firebase.database.core;
 
 import static org.junit.Assert.assertEquals;
 
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseOptions;
+import com.google.firebase.TestOnlyImplFirebaseTrampolines;
 import com.google.firebase.database.FirebaseDatabase;
+import com.google.firebase.testing.ServiceAccount;
+import com.google.firebase.testing.TestUtils;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -26,16 +31,26 @@ public class JvmPlatformTest {
 
   @Test
   public void userAgentHasCorrectParts() {
-    Context cfg = new DatabaseConfig();
-    cfg.freeze();
-    String userAgent = cfg.getUserAgent();
-    String[] parts = userAgent.split("/");
-    assertEquals(5, parts.length);
-    assertEquals("Firebase", parts[0]); // Firebase
-    assertEquals(Constants.WIRE_PROTOCOL_VERSION, parts[1]); // Wire protocol version
-    assertEquals(FirebaseDatabase.getSdkVersion(), parts[2]); // SDK version
-    assertEquals(System.getProperty("java.version", "Unknown"), parts[3]); // Java "OS" version
-    assertEquals(Platform.DEVICE, parts[4]); // AdminJava
+    FirebaseOptions options = new FirebaseOptions.Builder()
+        .setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
+        .build();
+    FirebaseApp app = FirebaseApp.initializeApp(options, "userAgentApp");
+
+    try {
+      Context cfg = new DatabaseConfig();
+      cfg.firebaseApp = app;
+      cfg.freeze();
+      String userAgent = cfg.getUserAgent();
+      String[] parts = userAgent.split("/");
+      assertEquals(5, parts.length);
+      assertEquals("Firebase", parts[0]); // Firebase
+      assertEquals(Constants.WIRE_PROTOCOL_VERSION, parts[1]); // Wire protocol version
+      assertEquals(FirebaseDatabase.getSdkVersion(), parts[2]); // SDK version
+      assertEquals(System.getProperty("java.version", "Unknown"), parts[3]); // Java "OS" version
+      assertEquals(Platform.DEVICE, parts[4]); // AdminJava
+    } finally {
+      TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+    }
   }
 
   @Test
diff --git a/src/test/java/com/google/firebase/database/core/SyncPointTest.java b/src/test/java/com/google/firebase/database/core/SyncPointTest.java
index e6f102e..4d70fd0 100644
--- a/src/test/java/com/google/firebase/database/core/SyncPointTest.java
+++ b/src/test/java/com/google/firebase/database/core/SyncPointTest.java
@@ -18,11 +18,11 @@ package com.google.firebase.database.core;
 
 import static com.google.common.base.Preconditions.checkState;
 
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.common.io.CharStreams;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
-import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.database.DataSnapshot;
 import com.google.firebase.database.DatabaseError;
 import com.google.firebase.database.DatabaseReference;
@@ -69,7 +69,7 @@ public class SyncPointTest {
   public static void setUpClass() throws IOException {
     testApp = FirebaseApp.initializeApp(
         new FirebaseOptions.Builder()
-            .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+            .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
             .setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
             .build());
   }
diff --git a/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java b/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java
index 541b7a3..b06cfb8 100644
--- a/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java
+++ b/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java
@@ -26,10 +26,10 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
-import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.database.core.CompoundWrite;
 import com.google.firebase.database.core.Path;
 import com.google.firebase.database.core.view.CacheNode;
@@ -57,7 +57,7 @@ public class DefaultPersistenceManagerTest {
   public static void setUpClass() throws IOException {
     testApp = FirebaseApp.initializeApp(
         new FirebaseOptions.Builder()
-            .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+            .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
             .setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
             .build());
   }
diff --git a/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java b/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java
index 971e4e7..1135d98 100644
--- a/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java
+++ b/src/test/java/com/google/firebase/database/core/persistence/KeepSyncedTestIT.java
@@ -156,7 +156,7 @@ public class KeepSyncedTestIT {
     }
 
     // Should trigger our listener.
-    ref.setValue("done");
+    ref.setValueAsync("done");
     readFuture.timedGet();
   }
 
@@ -270,7 +270,7 @@ public class KeepSyncedTestIT {
 
       // By now, if we had it synced we should have gotten an event with the wrong value
       // Write a new value so the value event listener will be triggered
-      ref.setValue(nextValue);
+      ref.setValueAsync(nextValue);
       readFuture.timedGet();
     } finally {
       // All good, go back online
diff --git a/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java b/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java
index 7d9d334..ce9a641 100644
--- a/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java
+++ b/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java
@@ -19,10 +19,10 @@ package com.google.firebase.database.core.persistence;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
-import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.database.DatabaseError;
 import com.google.firebase.database.TestHelpers;
 import com.google.firebase.database.annotations.NotNull;
@@ -74,7 +74,7 @@ public class RandomPersistenceTest {
   public static void setUpClass() throws IOException {
     testApp = FirebaseApp.initializeApp(
         new FirebaseOptions.Builder()
-            .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+            .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
             .setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
             .build());
   }
diff --git a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java
index 635f7cf..2d7a581 100644
--- a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java
@@ -21,10 +21,10 @@ import static org.junit.Assert.assertTrue;
 
 import com.google.api.core.ApiFutureCallback;
 import com.google.api.core.ApiFutures;
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.common.collect.ImmutableMap;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
-import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.database.DataSnapshot;
 import com.google.firebase.database.DatabaseError;
 import com.google.firebase.database.DatabaseReference;
@@ -79,7 +79,7 @@ public class FirebaseDatabaseAuthTestIT {
     FirebaseOptions options =
         new FirebaseOptions.Builder()
             .setDatabaseUrl(IntegrationTestUtils.getDatabaseUrl())
-            .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.NONE.asStream()))
+            .setCredentials(GoogleCredentials.fromStream(ServiceAccount.NONE.asStream()))
             .build();
     FirebaseApp app = FirebaseApp.initializeApp(options, "DatabaseServerAuthTestNoRole");
     FirebaseDatabase db = FirebaseDatabase.getInstance(app);
diff --git a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java
index 8b91c3e..f6451d9 100644
--- a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java
@@ -19,9 +19,9 @@ package com.google.firebase.database.integration;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
-import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.database.ChildEventListener;
 import com.google.firebase.database.DataSnapshot;
 import com.google.firebase.database.DatabaseError;
@@ -33,7 +33,6 @@ import com.google.firebase.database.TestFailure;
 import com.google.firebase.database.TestHelpers;
 import com.google.firebase.database.ValueEventListener;
 import com.google.firebase.database.future.ReadFuture;
-import com.google.firebase.tasks.Tasks;
 import com.google.firebase.testing.IntegrationTestUtils;
 import com.google.firebase.testing.TestUtils;
 
@@ -165,8 +164,7 @@ public class FirebaseDatabaseTestIT {
       TestFailure {
     FirebaseDatabase db = FirebaseDatabase.getInstance(masterApp);
     DatabaseReference ref = db.getReference("testSetValue");
-    Tasks.await(ref.setValue("foo"), TestUtils.TEST_TIMEOUT_MILLIS,
-        TimeUnit.MILLISECONDS);
+    ref.setValueAsync("foo").get(TestUtils.TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
     ReadFuture readFuture = ReadFuture.untilEquals(ref, "foo");
     readFuture.timedWait();
   }
@@ -178,7 +176,7 @@ public class FirebaseDatabaseTestIT {
     List<DatabaseReference> ref = IntegrationTestUtils.getRandomNode(app, 2);
     DatabaseReference writer = ref.get(0);
     DatabaseReference reader = ref.get(1);
-    writer.setValue("test");
+    writer.setValueAsync("test");
     TestHelpers.waitForRoundtrip(writer.getRoot());
     ReadFuture.untilEquals(reader, "test").timedWait();
 
@@ -191,14 +189,14 @@ public class FirebaseDatabaseTestIT {
     }
 
     try {
-      writer.setValue("foo");
+      writer.setValueAsync("foo");
       fail("No error thrown for deleted app");
     } catch (IllegalStateException expected) {
       // ignore
     }
 
     try {
-      writer.updateChildren(MapBuilder.of("a", 1));
+      writer.updateChildrenAsync(MapBuilder.of("a", 1));
       fail("No error thrown for deleted app");
     } catch (IllegalStateException expected) {
       // ignore
@@ -250,7 +248,7 @@ public class FirebaseDatabaseTestIT {
     ref = IntegrationTestUtils.getRandomNode(app, 2);
     writer = ref.get(0);
     reader = ref.get(1);
-    writer.setValue("test2");
+    writer.setValueAsync("test2");
     TestHelpers.waitForRoundtrip(writer.getRoot());
     ReadFuture.untilEquals(reader, "test2").timedWait();
   }
@@ -259,7 +257,7 @@ public class FirebaseDatabaseTestIT {
     try {
       FirebaseOptions options = new FirebaseOptions.Builder()
           .setDatabaseUrl(dbUrl)
-          .setCredential(FirebaseCredentials.fromCertificate(
+          .setCredentials(GoogleCredentials.fromStream(
               IntegrationTestUtils.getServiceAccountCertificate()))
           .build();
       return FirebaseApp.initializeApp(options, name);
@@ -271,7 +269,7 @@ public class FirebaseDatabaseTestIT {
   private static FirebaseApp appWithoutDbUrl(String name) {
     try {
       FirebaseOptions options = new FirebaseOptions.Builder()
-          .setCredential(FirebaseCredentials.fromCertificate(
+          .setCredentials(GoogleCredentials.fromStream(
               IntegrationTestUtils.getServiceAccountCertificate()))
           .build();
       return FirebaseApp.initializeApp(options, name);
diff --git a/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java b/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java
index 8d67886..128194b 100644
--- a/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java
@@ -426,8 +426,8 @@ public class OrderByTestIT {
               public void onChildChanged(DataSnapshot snap, String previousChildName) {}
             });
 
-    ref.setValue(initial);
-    ref.child("greg/nuggets").setValue(57);
+    ref.setValueAsync(initial);
+    ref.child("greg/nuggets").setValueAsync(57);
 
     TestHelpers.waitFor(semaphore);
 
@@ -614,33 +614,33 @@ public class OrderByTestIT {
     TestHelpers.waitFor(semaphore, 4);
     Assert.assertEquals(4, reads[0]);
 
-    ref.setValue(1);
+    ref.setValueAsync(1);
 
     TestHelpers.waitFor(semaphore, 4);
     Assert.assertEquals(8, reads[0]);
 
     ref.removeEventListener(fooListener);
-    ref.setValue(2);
+    ref.setValueAsync(2);
 
     TestHelpers.waitFor(semaphore, 3);
     Assert.assertEquals(11, reads[0]);
 
     // Should be a no-op resulting in 3 more reads
     ref.orderByChild("foo").removeEventListener(bazListener);
-    ref.setValue(3);
+    ref.setValueAsync(3);
 
     TestHelpers.waitFor(semaphore, 3);
     Assert.assertEquals(14, reads[0]);
 
     ref.orderByChild("bar").removeEventListener(barListener);
-    ref.setValue(4);
+    ref.setValueAsync(4);
     TestHelpers.waitFor(semaphore, 2);
     Assert.assertEquals(16, reads[0]);
 
     // Now, remove everything
     ref.removeEventListener(bazListener);
     ref.removeEventListener(defaultListener);
-    ref.setValue(5);
+    ref.setValueAsync(5);
 
     // No more reads
     TestHelpers.waitForRoundtrip(ref);
@@ -672,7 +672,7 @@ public class OrderByTestIT {
                   }
                 });
 
-    ref.setValue(initial);
+    ref.setValueAsync(initial);
     TestHelpers.waitFor(semaphore, 2);
     Assert.assertEquals(Arrays.asList("c", "a"), snapshotNames);
     Assert.assertEquals(Arrays.asList(null, "c"), prevNames);
@@ -680,7 +680,7 @@ public class OrderByTestIT {
     Map<String, Object> updates = new HashMap<>();
     updates.put("b", MapBuilder.of("value", 4));
     updates.put("d", MapBuilder.of("value", 2));
-    ref.updateChildren(updates);
+    ref.updateChildrenAsync(updates);
 
     TestHelpers.waitFor(semaphore, 2);
     Assert.assertEquals(Arrays.asList("c", "a", "d", "b"), snapshotNames);
@@ -732,7 +732,7 @@ public class OrderByTestIT {
     Assert.assertEquals(expected1, snapshots.get(0).getValue());
 
     // update child which should trigger value event
-    writer.child("one/index").setValue(4);
+    writer.child("one/index").setValueAsync(4);
     TestHelpers.waitFor(semaphore);
 
     Assert.assertEquals(2, snapshots.size());
diff --git a/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java b/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java
index 6794b4c..9c76198 100644
--- a/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/RealtimeTestIT.java
@@ -803,7 +803,7 @@ public class RealtimeTestIT {
 
     DatabaseReference.goOffline();
     for (int i = 0; i < 100; i++) {
-      ref.setValue(i);
+      ref.setValueAsync(i);
     }
     // This should be the last write and the actual value
     WriteFuture future = new WriteFuture(ref, 100);
diff --git a/src/test/java/com/google/firebase/database/integration/RulesTestIT.java b/src/test/java/com/google/firebase/database/integration/RulesTestIT.java
index f7d1793..1745ce2 100644
--- a/src/test/java/com/google/firebase/database/integration/RulesTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/RulesTestIT.java
@@ -22,10 +22,10 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
-import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.database.DataSnapshot;
 import com.google.firebase.database.DatabaseError;
 import com.google.firebase.database.DatabaseReference;
@@ -36,20 +36,19 @@ import com.google.firebase.database.TestFailure;
 import com.google.firebase.database.TestHelpers;
 import com.google.firebase.database.TestTokenProvider;
 import com.google.firebase.database.ValueEventListener;
+import com.google.firebase.database.core.AuthTokenProvider;
 import com.google.firebase.database.core.DatabaseConfig;
 import com.google.firebase.database.core.RepoManager;
 import com.google.firebase.database.future.ReadFuture;
 import com.google.firebase.database.future.WriteFuture;
 import com.google.firebase.database.util.JsonMapper;
 import com.google.firebase.testing.IntegrationTestUtils;
-import com.google.firebase.testing.TestUtils;
 
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -105,8 +104,8 @@ public class RulesTestIT {
     // Init app with non-admin privileges
     Map<String, Object> auth = MapBuilder.of("uid", "my-service-worker");
     FirebaseOptions options = new FirebaseOptions.Builder()
-        .setCredential(FirebaseCredentials
-            .fromCertificate(IntegrationTestUtils.getServiceAccountCertificate()))
+        .setCredentials(GoogleCredentials.fromStream(
+            IntegrationTestUtils.getServiceAccountCertificate()))
         .setDatabaseUrl(IntegrationTestUtils.getDatabaseUrl())
         .setDatabaseAuthVariableOverride(auth)
         .build();
@@ -211,9 +210,9 @@ public class RulesTestIT {
           }
         });
 
-    writer.child("read_and_write").setValue(42);
+    writer.child("read_and_write").setValueAsync(42);
     TestHelpers.waitFor(semaphore);
-    writer.child("read_and_write").setValue(84);
+    writer.child("read_and_write").setValueAsync(84);
     TestHelpers.waitFor(semaphore);
     reader.child("read_and_write").removeEventListener(listener);
   }
@@ -251,7 +250,7 @@ public class RulesTestIT {
           }
         });
 
-    writer.child("read_and_write").setValue(42);
+    writer.child("read_and_write").setValueAsync(42);
     TestHelpers.waitFor(semaphore);
     reader.addValueEventListener(new ValueEventListener() {
       @Override
@@ -267,7 +266,7 @@ public class RulesTestIT {
 
     TestHelpers.waitFor(semaphore);
 
-    writer.child("read_and_write").setValue(84);
+    writer.child("read_and_write").setValueAsync(84);
     TestHelpers.waitFor(semaphore);
     reader.child("read_and_write").removeEventListener(listener);
   }
@@ -299,12 +298,12 @@ public class RulesTestIT {
     assertTrue(valueHit.get());
     assertEquals(1, value.get());
 
-    writer.child("revocable/public").setValue(false);
-    writer.child("revocable/data").setValue(2);
+    writer.child("revocable/public").setValueAsync(false);
+    writer.child("revocable/data").setValueAsync(2);
     TestHelpers.waitFor(semaphore);
     assertTrue(valueHit.get());
 
-    writer.child("revocable/public").setValue(true);
+    writer.child("revocable/public").setValueAsync(true);
     new WriteFuture(writer.child("revocable/data"), 3).timedGet();
 
     // Ok, the listen was cancelled, create a new one now.
@@ -416,17 +415,21 @@ public class RulesTestIT {
   @Test
   public void testAuthenticatedImmediatelyAfterTokenChange() throws Exception {
     DatabaseConfig config = TestHelpers.getDatabaseConfig(masterApp);
-    TestTokenProvider provider = new TestTokenProvider(TestHelpers.getExecutorService(config));
-    config.setAuthTokenProvider(provider);
-
-    DatabaseReference root = FirebaseDatabase.getInstance(masterApp).getReference();
-    DatabaseReference ref = root.child(writer.getPath().toString());
-
-    String token = TestOnlyImplFirebaseTrampolines.getToken(masterApp, true).get(
-        TestUtils.TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS).getToken();
-    provider.setToken(token);
-
-    DatabaseError err = new WriteFuture(ref.child("any_auth"), true).timedGet();
-    assertNull(err);
+    AuthTokenProvider originalProvider = config.getAuthTokenProvider();
+    try {
+      TestTokenProvider provider = new TestTokenProvider(TestHelpers.getExecutorService(config));
+      config.setAuthTokenProvider(provider);
+
+      DatabaseReference root = FirebaseDatabase.getInstance(masterApp).getReference();
+      DatabaseReference ref = root.child(writer.getPath().toString());
+
+      String token = TestOnlyImplFirebaseTrampolines.getToken(masterApp, true);
+      provider.setToken(token);
+
+      DatabaseError err = new WriteFuture(ref.child("any_auth"), true).timedGet();
+      assertNull(err);
+    } finally {
+      config.setAuthTokenProvider(originalProvider);
+    }
   }
 }
diff --git a/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java b/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java
index f958d95..db46d36 100644
--- a/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java
@@ -1938,10 +1938,6 @@ public class TransactionTestIT {
       @Override
       public void addTokenChangeListener(TokenChangeListener listener) {
       }
-
-      @Override
-      public void removeTokenChangeListener(TokenChangeListener listener) {
-      }
     });
 
     // Queue a transaction offline.
diff --git a/src/test/java/com/google/firebase/internal/FirebaseAppStoreTest.java b/src/test/java/com/google/firebase/internal/FirebaseAppStoreTest.java
index 2f0438a..4b3699d 100644
--- a/src/test/java/com/google/firebase/internal/FirebaseAppStoreTest.java
+++ b/src/test/java/com/google/firebase/internal/FirebaseAppStoreTest.java
@@ -18,10 +18,10 @@ package com.google.firebase.internal;
 
 import static org.junit.Assert.assertTrue;
 
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
-import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.testing.FirebaseAppRule;
 import com.google.firebase.testing.ServiceAccount;
 import com.google.firebase.testing.TestUtils;
@@ -36,7 +36,7 @@ public class FirebaseAppStoreTest {
   private static final FirebaseOptions ALL_VALUES_OPTIONS =
       new FirebaseOptions.Builder()
           .setDatabaseUrl(FIREBASE_DB_URL)
-          .setCredential(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
+          .setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
           .build();
 
   @Rule public FirebaseAppRule firebaseAppRule = new FirebaseAppRule();
@@ -56,7 +56,7 @@ public class FirebaseAppStoreTest {
     TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
     FirebaseOptions options =
         new FirebaseOptions.Builder()
-            .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+            .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
             .build();
     FirebaseApp.initializeApp(options, name);
   }
@@ -67,7 +67,7 @@ public class FirebaseAppStoreTest {
     TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
     FirebaseOptions options =
         new FirebaseOptions.Builder()
-            .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+            .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
             .build();
     FirebaseApp.initializeApp(options);
   }
diff --git a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java
index fb39c36..6bf3af4 100644
--- a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java
+++ b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java
@@ -19,7 +19,6 @@ package com.google.firebase.testing;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.google.api.core.ApiFuture;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.CharStreams;
@@ -28,14 +27,12 @@ import com.google.firebase.FirebaseOptions;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
 import com.google.firebase.database.DatabaseReference;
 import com.google.firebase.database.FirebaseDatabase;
-import com.google.firebase.internal.GetTokenResult;
 import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
@@ -102,7 +99,7 @@ public class IntegrationTestUtils {
           new FirebaseOptions.Builder()
               .setDatabaseUrl(getDatabaseUrl())
               .setStorageBucket(getStorageBucket())
-              .setCredential(TestUtils.getCertCredential(getServiceAccountCertificate()))
+              .setCredentials(TestUtils.getCertCredential(getServiceAccountCertificate()))
               .build();
       masterApp = FirebaseApp.initializeApp(options);
     }
@@ -113,7 +110,7 @@ public class IntegrationTestUtils {
     FirebaseOptions options =
         new FirebaseOptions.Builder()
             .setDatabaseUrl(getDatabaseUrl())
-            .setCredential(TestUtils.getCertCredential(getServiceAccountCertificate()))
+            .setCredentials(TestUtils.getCertCredential(getServiceAccountCertificate()))
             .build();
     return FirebaseApp.initializeApp(options, name);
   }
@@ -164,12 +161,7 @@ public class IntegrationTestUtils {
     private String getToken() {
       // TODO: We should consider exposing getToken (or similar) publicly for the
       // purpose of servers doing authenticated REST requests like this.
-      ApiFuture<GetTokenResult> future = TestOnlyImplFirebaseTrampolines.getToken(app, false);
-      try {
-        return future.get().getToken();
-      } catch (ExecutionException | InterruptedException e) {
-        throw new RuntimeException(e);
-      }
+      return TestOnlyImplFirebaseTrampolines.getToken(app, false);
     }
   }
   
diff --git a/src/test/java/com/google/firebase/testing/TestUtils.java b/src/test/java/com/google/firebase/testing/TestUtils.java
index c5bccf1..12fc4ba 100644
--- a/src/test/java/com/google/firebase/testing/TestUtils.java
+++ b/src/test/java/com/google/firebase/testing/TestUtils.java
@@ -19,9 +19,8 @@ package com.google.firebase.testing;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.api.client.json.webtoken.JsonWebSignature;
+import com.google.auth.oauth2.GoogleCredentials;
 import com.google.common.io.CharStreams;
-import com.google.firebase.auth.FirebaseCredential;
-import com.google.firebase.auth.FirebaseCredentials;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -80,9 +79,9 @@ public class TestUtils {
     }
   }
 
-  public static FirebaseCredential getCertCredential(InputStream stream) {
+  public static GoogleCredentials getCertCredential(InputStream stream) {
     try {
-      return FirebaseCredentials.fromCertificate(stream);
+      return GoogleCredentials.fromStream(stream);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }