Repository: firebase/firebase-admin-java
Pull request #524 (2021-04-08 19:55:01)

chore: Add nightly build workflow
- Add nightly build workflow.
- Add a failing test to trigger the email notifications.

Note: Adding `release:stage` label to trigger integration tests. Integration tests should fail in this PR.


Patch:
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
new file mode 100644
index 0000000..e89d1db
--- /dev/null
+++ b/.github/workflows/nightly.yml
@@ -0,0 +1,83 @@
+# Copyright 2021 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.
+
+name: Nightly Builds
+
+on:
+  # Runs every day at 06:10 AM (PT) and 08:10 PM (PT) / 04:10 AM (UTC) and 02:10 PM (UTC)
+  # or on 'firebase_nightly_build' repository dispatch event.
+  schedule:
+    - cron: "10 4,14 * * *"
+  repository_dispatch:
+    types: [firebase_nightly_build]
+
+jobs:
+  nightly:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Checkout source for staging
+      uses: actions/checkout@v2
+      with:
+        ref: ${{ github.event.client_payload.ref || github.ref }}
+
+    - name: Set up JDK 1.7
+      uses: actions/setup-java@v1
+      with:
+        java-version: 1.7
+
+    - name: Compile, test and package
+      run: ./.github/scripts/package_artifacts.sh
+      env:
+        FIREBASE_SERVICE_ACCT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCT_KEY }}
+        FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}
+
+    # Attach the packaged artifacts to the workflow output. These can be manually
+    # downloaded for later inspection if necessary.
+    - name: Archive artifacts
+      uses: actions/upload-artifact@v1
+      with:
+        name: dist
+        path: dist
+
+    - name: Send email on failure
+      if: failure()
+      uses: firebase/firebase-admin-node/.github/actions/send-email
+      with:
+        api-key: ${{ secrets.OSS_BOT_MAILGUN_KEY }}
+        domain: ${{ secrets.OSS_BOT_MAILGUN_DOMAIN }}
+        from: 'GitHub <admin-github@${{ secrets.OSS_BOT_MAILGUN_DOMAIN }}>'
+        to: ${{ secrets.FIREBASE_ADMIN_GITHUB_EMAIL }}
+        subject: 'Nightly build ${{github.run_id}} of ${{github.repository}} failed!'
+        html: >
+          <b>Nightly workflow ${{github.run_id}} failed on: ${{github.repository}}</b>
+          <br /><br />Navigate to the 
+          <a href="https://github.com/firebase/firebase-admin-java/actions/runs/${{github.run_id}}">failed workflow</a>.
+      continue-on-error: true
+
+    - name: Send email on cancelled
+      if: cancelled()
+      uses: firebase/firebase-admin-node/.github/actions/send-email
+      with:
+        api-key: ${{ secrets.OSS_BOT_MAILGUN_KEY }}
+        domain: ${{ secrets.OSS_BOT_MAILGUN_DOMAIN }}
+        from: 'GitHub <admin-github@${{ secrets.OSS_BOT_MAILGUN_DOMAIN }}>'
+        to: ${{ secrets.FIREBASE_ADMIN_GITHUB_EMAIL }}
+        subject: 'Nightly build ${{github.run_id}} of ${{github.repository}} cancelled!'
+        html: >
+          <b>Nightly workflow ${{github.run_id}} cancelled on: ${{github.repository}}</b>
+          <br /><br />Navigate to the 
+          <a href="https://github.com/firebase/firebase-admin-java/actions/runs/${{github.run_id}}">cancelled workflow</a>.
+      continue-on-error: true
diff --git a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java
index fc9c8df..0967aca 100644
--- a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java
+++ b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java
@@ -18,6 +18,7 @@ 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.firebase.auth.internal.Utils.isEmulatorMode;
 
 import com.google.api.client.json.JsonFactory;
 import com.google.api.client.util.Clock;
@@ -309,7 +310,7 @@ public abstract class AbstractFirebaseAuth {
   @VisibleForTesting
   FirebaseTokenVerifier getIdTokenVerifier(boolean checkRevoked) {
     FirebaseTokenVerifier verifier = idTokenVerifier.get();
-    if (checkRevoked) {
+    if (checkRevoked || isEmulatorMode()) {
       FirebaseUserManager userManager = getUserManager();
       verifier = RevocationCheckDecorator.decorateIdTokenVerifier(verifier, userManager);
     }
@@ -389,7 +390,7 @@ public abstract class AbstractFirebaseAuth {
   @VisibleForTesting
   FirebaseTokenVerifier getSessionCookieVerifier(boolean checkRevoked) {
     FirebaseTokenVerifier verifier = cookieVerifier.get();
-    if (checkRevoked) {
+    if (checkRevoked || isEmulatorMode()) {
       FirebaseUserManager userManager = getUserManager();
       verifier = RevocationCheckDecorator.decorateSessionCookieVerifier(verifier, userManager);
     }
diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
index bbe741f..9a3e1ae 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
@@ -29,6 +29,7 @@ import com.google.api.client.util.ArrayMap;
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
 import com.google.firebase.ErrorCode;
+import com.google.firebase.auth.internal.Utils;
 import com.google.firebase.internal.Nullable;
 import java.io.IOException;
 import java.math.BigDecimal;
@@ -94,9 +95,12 @@ final class FirebaseTokenVerifierImpl implements FirebaseTokenVerifier {
    */
   @Override
   public FirebaseToken verifyToken(String token) throws FirebaseAuthException {
+    boolean isEmulatorMode = Utils.isEmulatorMode();
     IdToken idToken = parse(token);
-    checkContents(idToken);
-    checkSignature(idToken);
+    checkContents(idToken, isEmulatorMode);
+    if (!isEmulatorMode) {
+      checkSignature(idToken);
+    }
     FirebaseToken firebaseToken = new FirebaseToken(idToken.getPayload());
     checkTenantId(firebaseToken);
     return firebaseToken;
@@ -160,7 +164,8 @@ final class FirebaseTokenVerifierImpl implements FirebaseTokenVerifier {
     }
   }
 
-  private void checkContents(final IdToken idToken) throws FirebaseAuthException {
+  private void checkContents(final IdToken idToken, boolean isEmulatorMode)
+      throws FirebaseAuthException {
     final Header header = idToken.getHeader();
     final Payload payload = idToken.getPayload();
 
@@ -168,9 +173,9 @@ final class FirebaseTokenVerifierImpl implements FirebaseTokenVerifier {
     String errorMessage = null;
     AuthErrorCode errorCode = invalidTokenErrorCode;
 
-    if (header.getKeyId() == null) {
+    if (!isEmulatorMode && header.getKeyId() == null) {
       errorMessage = getErrorForTokenWithoutKid(header, payload);
-    } else if (!RS256.equals(header.getAlgorithm())) {
+    } else if (!isEmulatorMode && !RS256.equals(header.getAlgorithm())) {
       errorMessage = String.format(
           "Firebase %s has incorrect algorithm. Expected \"%s\" but got \"%s\".",
           shortName,
diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
index 554d017..103360c 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
@@ -41,6 +41,7 @@ import com.google.firebase.auth.internal.GetAccountInfoResponse;
 import com.google.firebase.auth.internal.ListOidcProviderConfigsResponse;
 import com.google.firebase.auth.internal.ListSamlProviderConfigsResponse;
 import com.google.firebase.auth.internal.UploadAccountResponse;
+import com.google.firebase.auth.internal.Utils;
 import com.google.firebase.internal.ApiClientUtils;
 import com.google.firebase.internal.HttpRequestInfo;
 import com.google.firebase.internal.NonNull;
@@ -72,6 +73,8 @@ final class FirebaseUserManager {
 
   private static final String ID_TOOLKIT_URL =
       "https://identitytoolkit.googleapis.com/%s/projects/%s";
+  private static final String ID_TOOLKIT_URL_EMULATOR =
+          "http://%s/identitytoolkit.googleapis.com/%s/projects/%s";
 
   private final String userMgtBaseUrl;
   private final String idpConfigMgtBaseUrl;
@@ -85,8 +88,8 @@ final class FirebaseUserManager {
             + "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
             + "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
     this.jsonFactory = checkNotNull(builder.jsonFactory, "JsonFactory must not be null");
-    final String idToolkitUrlV1 = String.format(ID_TOOLKIT_URL, "v1", projectId);
-    final String idToolkitUrlV2 = String.format(ID_TOOLKIT_URL, "v2", projectId);
+    final String idToolkitUrlV1 = getIdToolkitUrl(projectId, "v1");
+    final String idToolkitUrlV2 = getIdToolkitUrl(projectId, "v2");
     final String tenantId = builder.tenantId;
     if (tenantId == null) {
       this.userMgtBaseUrl = idToolkitUrlV1;
@@ -100,6 +103,13 @@ final class FirebaseUserManager {
     this.httpClient = new AuthHttpClient(jsonFactory, builder.requestFactory);
   }
 
+  private String getIdToolkitUrl(String projectId, String version) {
+    if (Utils.isEmulatorMode()) {
+      return String.format(ID_TOOLKIT_URL_EMULATOR, Utils.getEmulatorHost(), version, projectId);
+    }
+    return String.format(ID_TOOLKIT_URL, version, projectId);
+  }
+
   @VisibleForTesting
   void setInterceptor(HttpResponseInterceptor interceptor) {
     httpClient.setInterceptor(interceptor);
diff --git a/src/main/java/com/google/firebase/auth/OidcProviderConfig.java b/src/main/java/com/google/firebase/auth/OidcProviderConfig.java
index 2693178..0a4f7c0 100644
--- a/src/main/java/com/google/firebase/auth/OidcProviderConfig.java
+++ b/src/main/java/com/google/firebase/auth/OidcProviderConfig.java
@@ -18,8 +18,11 @@ package com.google.firebase.auth;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.api.client.json.GenericJson;
 import com.google.api.client.util.Key;
 import com.google.common.base.Strings;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Contains metadata associated with an OIDC Auth provider.
@@ -31,17 +34,35 @@ public final class OidcProviderConfig extends ProviderConfig {
   @Key("clientId")
   private String clientId;
 
+  @Key("clientSecret")
+  private String clientSecret;
+
   @Key("issuer")
   private String issuer;
 
+  @Key("responseType")
+  private GenericJson responseType;
+
   public String getClientId() {
     return clientId;
   }
 
+  public String getClientSecret() {
+    return clientSecret;
+  }
+
   public String getIssuer() {
     return issuer;
   }
 
+  public boolean isCodeResponseType() {
+    return (responseType.containsKey("code") && (boolean) responseType.get("code"));
+  }
+
+  public boolean isIdTokenResponseType() {
+    return (responseType.containsKey("idToken") && (boolean) responseType.get("idToken"));
+  }
+
   /**
    * Returns a new {@link UpdateRequest}, which can be used to update the attributes of this
    * provider config.
@@ -58,6 +79,13 @@ public final class OidcProviderConfig extends ProviderConfig {
         "Invalid OIDC provider ID (must be prefixed with 'oidc.'): " + providerId);
   }
 
+  static Map<String, Boolean> ensureResponseType(Map<String,Object> properties) {
+    if (properties.get("responseType") == null) {
+      properties.put("responseType", new HashMap<String, Boolean>());
+    }
+    return (Map<String, Boolean>) properties.get("responseType");
+  }
+
   /**
    * A specification class for creating a new OIDC Auth provider.
    *
@@ -99,6 +127,19 @@ public final class OidcProviderConfig extends ProviderConfig {
       return this;
     }
 
+    /**
+     * Sets the client secret for the new provider. This is required for the code flow.
+     *
+     * @param clientSecret A non-null, non-empty client secret string.
+     * @throws IllegalArgumentException If the client secret is null or empty.
+     */
+    public CreateRequest setClientSecret(String clientSecret) {
+      checkArgument(!Strings.isNullOrEmpty(clientSecret),
+          "Client Secret must not be null or empty.");
+      properties.put("clientSecret", clientSecret);
+      return this;
+    }
+
     /**
      * Sets the issuer for the new provider.
      *
@@ -113,6 +154,36 @@ public final class OidcProviderConfig extends ProviderConfig {
       return this;
     }
 
+    /**
+     * Sets whether to enable the code response flow for the new provider. By default, this is not
+     * enabled if no response type is specified.
+     *
+     * <p>A client secret must be set for this response type.
+     *
+     * <p>Having both the code and ID token response flows is currently not supported.
+     *
+     * @param enabled A boolean signifying whether the code response type is supported.
+     */
+    public CreateRequest setCodeResponseType(boolean enabled) {
+      Map<String, Boolean> map = ensureResponseType(properties);
+      map.put("code", enabled);
+      return this;
+    }
+
+    /**
+     * Sets whether to enable the ID token response flow for the new provider. By default, this is
+     * enabled if no response type is specified.
+     *
+     * <p>Having both the code and ID token response flows is currently not supported.
+     *
+     * @param enabled A boolean signifying whether the ID token response type is supported.
+     */
+    public CreateRequest setIdTokenResponseType(boolean enabled) {
+      Map<String, Boolean> map = ensureResponseType(properties);
+      map.put("idToken", enabled);
+      return this;
+    }
+
     CreateRequest getThis() {
       return this;
     }
@@ -156,6 +227,19 @@ public final class OidcProviderConfig extends ProviderConfig {
       return this;
     }
 
+    /**
+     * Sets the client secret for the new provider. This is required for the code flow.
+     *
+     * @param clientSecret A non-null, non-empty client secret string.
+     * @throws IllegalArgumentException If the client secret is null or empty.
+     */
+    public UpdateRequest setClientSecret(String clientSecret) {
+      checkArgument(!Strings.isNullOrEmpty(clientSecret),
+          "Client Secret must not be null or empty.");
+      properties.put("clientSecret", clientSecret);
+      return this;
+    }
+
     /**
      * Sets the issuer for the existing provider.
      *
@@ -170,6 +254,36 @@ public final class OidcProviderConfig extends ProviderConfig {
       return this;
     }
 
+    /**
+     * Sets whether to enable the code response flow for the new provider. By default, this is not
+     * enabled if no response type is specified.
+     *
+     * <p>A client secret must be set for this response type.
+     *
+     * <p>Having both the code and ID token response flows is currently not supported.
+     *
+     * @param enabled A boolean signifying whether the code response type is supported.
+     */
+    public UpdateRequest setCodeResponseType(boolean enabled) {
+      Map<String, Boolean> map = ensureResponseType(properties);
+      map.put("code", enabled);
+      return this;
+    }
+
+    /**
+     * Sets whether to enable the ID token response flow for the new provider. By default, this is
+     * enabled if no response type is specified.
+     *
+     * <p>Having both the code and ID token response flows is currently not supported.
+     *
+     * @param enabled A boolean signifying whether the ID token response type is supported.
+     */
+    public UpdateRequest setIdTokenResponseType(boolean enabled) {
+      Map<String, Boolean> map = ensureResponseType(properties);
+      map.put("idToken", enabled);
+      return this;
+    }
+
     UpdateRequest getThis() {
       return this;
     }
diff --git a/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java b/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java
index f167b09..543f955 100644
--- a/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java
+++ b/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java
@@ -22,6 +22,7 @@ import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseException;
 import com.google.firebase.ImplFirebaseTrampolines;
 import com.google.firebase.auth.FirebaseAuthException;
+import com.google.firebase.auth.internal.Utils;
 import com.google.firebase.internal.AbstractPlatformErrorHandler;
 import com.google.firebase.internal.ApiClientUtils;
 import com.google.firebase.internal.ErrorHandlingHttpClient;
@@ -108,6 +109,25 @@ public class CryptoSigners {
     }
   }
 
+  /**
+   * A {@link CryptoSigner} implementation that doesn't sign data. For use with the Auth Emulator
+   * only
+   */
+  public static class EmulatorCryptoSigner implements CryptoSigner {
+
+    private static final String ACCOUNT = "firebase-auth-emulator@example.com";
+
+    @Override
+    public byte[] sign(byte[] payload) {
+      return "".getBytes();
+    }
+
+    @Override
+    public String getAccount() {
+      return ACCOUNT;
+    }
+  }
+
   private static class IAMErrorHandler
       extends AbstractPlatformErrorHandler<FirebaseAuthException> {
 
@@ -126,6 +146,10 @@ public class CryptoSigners {
    * documented at go/firebase-admin-sign.
    */
   public static CryptoSigner getCryptoSigner(FirebaseApp firebaseApp) throws IOException {
+    if (Utils.isEmulatorMode()) {
+      return new EmulatorCryptoSigner();
+    }
+
     GoogleCredentials credentials = ImplFirebaseTrampolines.getCredentials(firebaseApp);
 
     // If the SDK was initialized with a service account, use it to sign bytes.
diff --git a/src/main/java/com/google/firebase/auth/internal/Utils.java b/src/main/java/com/google/firebase/auth/internal/Utils.java
new file mode 100644
index 0000000..487892d
--- /dev/null
+++ b/src/main/java/com/google/firebase/auth/internal/Utils.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright  2021 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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+
+public class Utils {
+  @VisibleForTesting
+  public static final String AUTH_EMULATOR_HOST = "FIREBASE_AUTH_EMULATOR_HOST";
+
+  public static boolean isEmulatorMode() {
+    return !Strings.isNullOrEmpty(getEmulatorHost());
+  }
+
+  public static String getEmulatorHost() {
+    return System.getenv(AUTH_EMULATOR_HOST);
+  }
+
+}
diff --git a/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java b/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java
index 5b776a4..56ef04e 100644
--- a/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java
+++ b/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java
@@ -31,6 +31,7 @@ import com.google.firebase.ImplFirebaseTrampolines;
 import com.google.firebase.auth.FirebaseAuthException;
 import com.google.firebase.auth.internal.AuthHttpClient;
 import com.google.firebase.auth.internal.ListTenantsResponse;
+import com.google.firebase.auth.internal.Utils;
 import com.google.firebase.internal.ApiClientUtils;
 import com.google.firebase.internal.HttpRequestInfo;
 import java.util.Map;
@@ -42,6 +43,9 @@ final class FirebaseTenantClient {
   private static final String ID_TOOLKIT_URL =
       "https://identitytoolkit.googleapis.com/%s/projects/%s";
 
+  private static final String ID_TOOLKIT_URL_EMULATOR =
+          "http://%s/identitytoolkit.googleapis.com/%s/projects/%s";
+
   private final String tenantMgtBaseUrl;
   private final AuthHttpClient httpClient;
 
@@ -58,10 +62,17 @@ final class FirebaseTenantClient {
         "Project ID is required to access the auth service. Use a service account credential or "
             + "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
             + "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
-    this.tenantMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v2", projectId);
+    this.tenantMgtBaseUrl = getTenantMgtBaseUrl(projectId);
     this.httpClient = new AuthHttpClient(jsonFactory, requestFactory);
   }
 
+  private String getTenantMgtBaseUrl(String projectId) {
+    if (Utils.isEmulatorMode()) {
+      return String.format(ID_TOOLKIT_URL_EMULATOR, Utils.getEmulatorHost(), "v2", projectId);
+    }
+    return String.format(ID_TOOLKIT_URL, "v2", projectId);
+  }
+
   void setInterceptor(HttpResponseInterceptor interceptor) {
     httpClient.setInterceptor(interceptor);
   }


Test patch:
diff --git a/src/test/java/com/google/firebase/auth/EmulatorFirebaseTokenVerifierImplTest.java b/src/test/java/com/google/firebase/auth/EmulatorFirebaseTokenVerifierImplTest.java
new file mode 100644
index 0000000..0cc12c6
--- /dev/null
+++ b/src/test/java/com/google/firebase/auth/EmulatorFirebaseTokenVerifierImplTest.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright  2019 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 static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.TEST_TOKEN_ISSUER;
+import static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.checkInvalidTokenException;
+import static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.fullyPopulatedBuilder;
+import static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.newPublicKeysManager;
+import static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.newTestTokenVerifier;
+import static com.google.firebase.auth.internal.Utils.AUTH_EMULATOR_HOST;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.firebase.ErrorCode;
+import com.google.firebase.testing.ServiceAccount;
+import com.google.firebase.testing.TestUtils;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class EmulatorFirebaseTokenVerifierImplTest {
+  private FirebaseTokenVerifier tokenVerifier;
+  private TestTokenFactory tokenFactory;
+
+  @Before
+  public void setUp() throws Exception {
+    // Set the Auth Emulator host prior to initialization
+    TestUtils.setEnvironmentVariables(ImmutableMap.of(AUTH_EMULATOR_HOST, "localhost:9099"));
+    ServiceAccount serviceAccount = ServiceAccount.EDITOR;
+    GooglePublicKeysManager publicKeysManager = newPublicKeysManager(serviceAccount.getCert());
+    this.tokenVerifier = newTestTokenVerifier(publicKeysManager);
+    this.tokenFactory = new TestTokenFactory(serviceAccount.getPrivateKey(), TEST_TOKEN_ISSUER);
+  }
+
+  @After
+  public void tearDown() {
+    TestUtils.unsetEnvironmentVariables(ImmutableSet.of(AUTH_EMULATOR_HOST));
+  }
+
+  @Test
+  public void testVerifyToken() throws Exception {
+    String token = tokenFactory.createUnsignedTokenForEmulator();
+
+    FirebaseToken firebaseToken = tokenVerifier.verifyToken(token);
+
+    assertEquals(TEST_TOKEN_ISSUER, firebaseToken.getIssuer());
+    assertEquals(TestTokenFactory.UID, firebaseToken.getUid());
+  }
+
+  @Test
+  public void testVerifyTokenWithoutKeyId() throws Exception {
+    String token = createTokenWithoutKeyId();
+
+    // Should not throw, even if missing kid
+    tokenVerifier.verifyToken(token);
+  }
+
+  @Test
+  public void testVerifyTokenIncorrectAlgorithm() throws Exception {
+    String token = createTokenWithIncorrectAlgorithm();
+
+    // Should not throw, even if missing algorithm
+    tokenVerifier.verifyToken(token);
+  }
+
+  @Test
+  public void testVerifyTokenWithoutAlgorithm() throws Exception {
+    String token = createTokenWithoutAlgorithm();
+
+    // Should not throw, even if algorithm is none
+    tokenVerifier.verifyToken(token);
+  }
+
+  @Test
+  public void testVerifyTokenIncorrectAudience() {
+    String token = createTokenWithIncorrectAudience();
+
+    try {
+      tokenVerifier.verifyToken(token);
+      fail("No error thrown for incorrect audience");
+    } catch (FirebaseAuthException e) {
+      String message = String.format("Firebase test token has incorrect \"aud\" (audience) claim. "
+              + "Expected \"%s\" but got \"invalid-audience\". "
+              + "Make sure the test token comes from the same Firebase project as the service "
+              + "account used to authenticate this SDK. "
+              + "See https://test.doc.url for details on how to retrieve a test token.",
+          TestTokenFactory.PROJECT_ID);
+      checkInvalidTokenException(e, message);
+    }
+  }
+
+  @Test
+  public void testVerifyTokenIncorrectIssuer() {
+    String token = createTokenWithIncorrectIssuer();
+
+    try {
+      tokenVerifier.verifyToken(token);
+      fail("No error thrown for incorrect issuer");
+    } catch (FirebaseAuthException e) {
+      String message = String.format("Firebase test token has incorrect \"iss\" (issuer) claim. "
+              + "Expected \"%s\" but got "
+              + "\"https://incorrect.issuer.prefix/%s\". Make sure the test token comes "
+              + "from the same Firebase project as the service account used to authenticate this "
+              + "SDK. See https://test.doc.url for details on how to retrieve a test token.",
+          TEST_TOKEN_ISSUER, TestTokenFactory.PROJECT_ID);
+      checkInvalidTokenException(e, message);
+    }
+  }
+
+  @Test
+  public void testVerifyTokenMissingSubject() {
+    String token = createTokenWithSubject(null);
+
+    try {
+      tokenVerifier.verifyToken(token);
+      fail("No error thrown for missing subject");
+    } catch (FirebaseAuthException e) {
+      String message = "Firebase test token has no \"sub\" (subject) claim. "
+          + "See https://test.doc.url for details on how to retrieve a test token.";
+      checkInvalidTokenException(e, message);
+    }
+  }
+
+  @Test
+  public void testVerifyTokenEmptySubject() {
+    String token = createTokenWithSubject("");
+
+    try {
+      tokenVerifier.verifyToken(token);
+      fail("No error thrown for empty subject");
+    } catch (FirebaseAuthException e) {
+      String message = "Firebase test token has an empty string \"sub\" (subject) claim. "
+          + "See https://test.doc.url for details on how to retrieve a test token.";
+      checkInvalidTokenException(e, message);
+    }
+  }
+
+  @Test
+  public void testVerifyTokenLongSubject() {
+    String token = createTokenWithSubject(Strings.repeat("a", 129));
+
+    try {
+      tokenVerifier.verifyToken(token);
+      fail("No error thrown for long subject");
+    } catch (FirebaseAuthException e) {
+      String message = "Firebase test token has \"sub\" (subject) claim longer "
+          + "than 128 characters. "
+          + "See https://test.doc.url for details on how to retrieve a test token.";
+      checkInvalidTokenException(e, message);
+    }
+  }
+
+  @Test
+  public void testVerifyTokenIssuedAtInFuture() {
+    long tenMinutesIntoTheFuture = (TestTokenFactory.CLOCK.currentTimeMillis() / 1000)
+        + TimeUnit.MINUTES.toSeconds(10);
+    String token = createTokenWithTimestamps(
+        tenMinutesIntoTheFuture,
+        tenMinutesIntoTheFuture + TimeUnit.HOURS.toSeconds(1));
+
+    try {
+      tokenVerifier.verifyToken(token);
+      fail("No error thrown for token issued in future");
+    } catch (FirebaseAuthException e) {
+      String message = "Firebase test token is not yet valid. "
+          + "See https://test.doc.url for details on how to retrieve a test token.";
+      checkInvalidTokenException(e, message);
+    }
+  }
+
+  @Test
+  public void testVerifyTokenExpired() {
+    long twoHoursInPast = (TestTokenFactory.CLOCK.currentTimeMillis() / 1000)
+        - TimeUnit.HOURS.toSeconds(2);
+    String token = createTokenWithTimestamps(
+        twoHoursInPast,
+        twoHoursInPast + TimeUnit.HOURS.toSeconds(1));
+
+    try {
+      tokenVerifier.verifyToken(token);
+      fail("No error thrown for expired token");
+    } catch (FirebaseAuthException e) {
+      String message = "Firebase test token has expired. "
+          + "Get a fresh test token and try again. "
+          + "See https://test.doc.url for details on how to retrieve a test token.";
+      FirebaseTokenVerifierImplTestUtils.checkException(e, message, AuthErrorCode.EXPIRED_ID_TOKEN);
+    }
+  }
+
+  @Test
+  public void testWithMalformedSignature() throws FirebaseAuthException {
+    String token = tokenFactory.createUnsignedTokenForEmulator();
+    String[] segments = token.split("\\.");
+    token = String.format("%s.%s.%s", segments[0], segments[1], "MalformedSignature");
+
+    // No need to assert. verifyToken should not throw
+    tokenVerifier.verifyToken(token);
+  }
+
+  @Test
+  public void testMalformedToken() {
+    try {
+      tokenVerifier.verifyToken("not.a.jwt");
+      fail("No error thrown for malformed token");
+    } catch (FirebaseAuthException e) {
+      String message = "Failed to parse Firebase test token. "
+          + "Make sure you passed a string that represents a complete and valid JWT. "
+          + "See https://test.doc.url for details on how to retrieve a test token.";
+      assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
+      assertEquals(message, e.getMessage());
+      assertTrue(e.getCause() instanceof IllegalArgumentException);
+      assertNull(e.getHttpResponse());
+      assertEquals(AuthErrorCode.INVALID_ID_TOKEN, e.getAuthErrorCode());
+    }
+  }
+
+  @Test
+  public void testVerifyTokenWithTenantId() throws FirebaseAuthException {
+    FirebaseToken firebaseToken = tokenVerifier
+        .verifyToken(createTokenWithTenantId("TENANT_1"));
+
+    assertEquals(TEST_TOKEN_ISSUER, firebaseToken.getIssuer());
+    assertEquals(TestTokenFactory.UID, firebaseToken.getUid());
+    assertEquals("TENANT_1", firebaseToken.getTenantId());
+  }
+
+  @Test
+  public void testVerifyTokenWithMatchingTenantId() throws FirebaseAuthException {
+    FirebaseToken firebaseToken = fullyPopulatedBuilder()
+        .setTenantId("TENANT_1")
+        .build().verifyToken(createTokenWithTenantId("TENANT_1"));
+
+    assertEquals(TEST_TOKEN_ISSUER, firebaseToken.getIssuer());
+    assertEquals(TestTokenFactory.UID, firebaseToken.getUid());
+    assertEquals("TENANT_1", firebaseToken.getTenantId());
+  }
+
+  @Test
+  public void testVerifyTokenDifferentTenantIds() {
+    try {
+      fullyPopulatedBuilder()
+          .setTenantId("TENANT_1")
+          .build().verifyToken(createTokenWithTenantId("TENANT_2"));
+      fail("No error thrown for mismatched tenant IDs");
+    } catch (FirebaseAuthException e) {
+      assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode());
+      assertEquals(
+          "The tenant ID ('TENANT_2') of the token did not match the expected value ('TENANT_1')",
+          e.getMessage());
+    }
+  }
+
+  @Test
+  public void testVerifyTokenNoTenantId() {
+    try {
+      fullyPopulatedBuilder()
+          .setTenantId("TENANT_1")
+          .build()
+          .verifyToken(tokenFactory.createUnsignedTokenForEmulator());
+      fail("No error thrown for missing tenant ID in token");
+    } catch (FirebaseAuthException e) {
+      assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode());
+      assertEquals(
+          "The tenant ID ('') of the token did not match the expected value ('TENANT_1')",
+          e.getMessage());
+    }
+  }
+
+  private String createTokenWithIncorrectAudience() {
+    return tokenFactory.createTokenWithIncorrectAudience(true);
+  }
+
+  private String createTokenWithoutKeyId() {
+    return tokenFactory.createTokenWithoutKeyId(true);
+  }
+
+  private String createTokenWithIncorrectAlgorithm() {
+    return tokenFactory.createTokenWithIncorrectAlgorithm(true);
+  }
+
+  private String createTokenWithoutAlgorithm() {
+    return tokenFactory.createTokenWithoutAlgorithm(true);
+  }
+
+  private String createTokenWithIncorrectIssuer() {
+    return tokenFactory.createTokenWithIncorrectIssuer(true);
+  }
+
+  private String createTokenWithSubject(String sub) {
+    return tokenFactory.createTokenWithSubject(sub, true);
+  }
+
+  private String createTokenWithTenantId(String tenantId) {
+    return tokenFactory.createTokenWithTenantId(tenantId, true);
+  }
+
+  private String createTokenWithTimestamps(long issuedAtSeconds, long expirationSeconds) {
+    return tokenFactory.createTokenWithTimestamps(issuedAtSeconds, expirationSeconds, true);
+  }
+}
diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
index 35fa21d..f474f04 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
@@ -703,12 +703,19 @@ public class FirebaseAuthIT {
             .setDisplayName("DisplayName")
             .setEnabled(true)
             .setClientId("ClientId")
-            .setIssuer("https://oidc.com/issuer"));
+            .setClientSecret("ClientSecret")
+            .setIssuer("https://oidc.com/issuer")
+            .setCodeResponseType(true)
+            .setIdTokenResponseType(false));
+
     assertEquals(providerId, config.getProviderId());
     assertEquals("DisplayName", config.getDisplayName());
     assertTrue(config.isEnabled());
     assertEquals("ClientId", config.getClientId());
+    assertEquals("ClientSecret", config.getClientSecret());
     assertEquals("https://oidc.com/issuer", config.getIssuer());
+    assertTrue(config.isCodeResponseType());
+    assertFalse(config.isIdTokenResponseType());
 
     // Get provider config
     config = auth.getOidcProviderConfigAsync(providerId).get();
@@ -716,7 +723,10 @@ public class FirebaseAuthIT {
     assertEquals("DisplayName", config.getDisplayName());
     assertTrue(config.isEnabled());
     assertEquals("ClientId", config.getClientId());
+    assertEquals("ClientSecret", config.getClientSecret());
     assertEquals("https://oidc.com/issuer", config.getIssuer());
+    assertTrue(config.isCodeResponseType());
+    assertFalse(config.isIdTokenResponseType());
 
     // Update provider config
     OidcProviderConfig.UpdateRequest updateRequest =
@@ -724,13 +734,20 @@ public class FirebaseAuthIT {
             .setDisplayName("NewDisplayName")
             .setEnabled(false)
             .setClientId("NewClientId")
-            .setIssuer("https://oidc.com/new-issuer");
+            .setClientSecret("NewClientSecret")
+            .setIssuer("https://oidc.com/new-issuer")
+            .setCodeResponseType(false)
+            .setIdTokenResponseType(true);
+
     config = auth.updateOidcProviderConfigAsync(updateRequest).get();
     assertEquals(providerId, config.getProviderId());
     assertEquals("NewDisplayName", config.getDisplayName());
     assertFalse(config.isEnabled());
     assertEquals("NewClientId", config.getClientId());
+    assertEquals("NewClientSecret", config.getClientSecret());
     assertEquals("https://oidc.com/new-issuer", config.getIssuer());
+    assertTrue(config.isIdTokenResponseType());
+    assertFalse(config.isCodeResponseType());
 
     // Delete provider config
     temporaryProviderConfig.deleteOidcProviderConfig(providerId);
diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
index bd7aa0f..d385e7e 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
@@ -31,6 +31,7 @@ import com.google.api.core.ApiFuture;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.firebase.ErrorCode;
 
 import com.google.firebase.FirebaseApp;
@@ -56,9 +57,12 @@ public class FirebaseAuthTest {
       ErrorCode.INVALID_ARGUMENT, "Test error message", null, null, null);
   private static final long VALID_SINCE = 1494364393;
   private static final String TEST_USER = "testUser";
+  private static final String AUTH_EMULATOR = "localhost:9099";
 
   @After
   public void cleanup() {
+    // Cleanup for tests on Auth Emulator
+    TestUtils.unsetEnvironmentVariables(ImmutableSet.of("FIREBASE_AUTH_EMULATOR_HOST"));
     TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
   }
 
@@ -262,6 +266,45 @@ public class FirebaseAuthTest {
     assertEquals("idtoken", tokenVerifier.getLastTokenString());
   }
 
+  @Test
+  public void testVerifyIdTokenWithEmulator() throws Exception {
+    // Enable emulator mode
+    TestUtils.setEnvironmentVariables(
+        ImmutableMap.of("FIREBASE_AUTH_EMULATOR_HOST", AUTH_EMULATOR));
+    MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult(
+        getFirebaseToken(VALID_SINCE + 1000));
+    FirebaseAuth auth = getAuthForIdTokenVerificationWithRevocationCheck(tokenVerifier);
+
+    FirebaseToken firebaseToken = auth.verifyIdToken("idtoken", false);
+
+    assertEquals("testUser", firebaseToken.getUid());
+    assertEquals("idtoken", tokenVerifier.getLastTokenString());
+  }
+
+  @Test
+  public void testVerifyIdTokenFailureWithEmulator() {
+    // Enable emulator mode
+    TestUtils.setEnvironmentVariables(
+        ImmutableMap.of("FIREBASE_AUTH_EMULATOR_HOST", AUTH_EMULATOR));
+    MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult(
+        getFirebaseToken(VALID_SINCE - 1000));
+    FirebaseAuth auth = getAuthForIdTokenVerificationWithRevocationCheck(tokenVerifier);
+
+    try {
+      // Should throw if token is revoked, even if checkRevoked is false.
+      auth.verifyIdToken("idtoken", false);
+      fail("No error thrown for revoked ID token");
+    } catch (FirebaseAuthException e) {
+      assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
+      assertEquals("Firebase id token is revoked.", e.getMessage());
+      assertNull(e.getCause());
+      assertNull(e.getHttpResponse());
+      assertEquals(AuthErrorCode.REVOKED_ID_TOKEN, e.getAuthErrorCode());
+    }
+
+    assertEquals("idtoken", tokenVerifier.getLastTokenString());
+  }
+
   @Test
   public void testVerifyIdTokenFailure() {
     MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(testException);
diff --git a/src/test/java/com/google/firebase/auth/FirebaseTokenUtilsTest.java b/src/test/java/com/google/firebase/auth/FirebaseTokenUtilsTest.java
index f452091..ccc1ad6 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseTokenUtilsTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseTokenUtilsTest.java
@@ -27,10 +27,13 @@ import com.google.api.client.json.gson.GsonFactory;
 import com.google.api.client.testing.http.FixedClock;
 import com.google.api.client.util.Clock;
 import com.google.auth.oauth2.GoogleCredentials;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseOptions;
 import com.google.firebase.TestOnlyImplFirebaseTrampolines;
+import com.google.firebase.testing.TestUtils;
 import org.junit.After;
 import org.junit.Rule;
 import org.junit.Test;
@@ -41,12 +44,14 @@ public class FirebaseTokenUtilsTest {
   private static final Clock CLOCK = new FixedClock(2002000L * 1000);
   private static final String TEST_PROJECT_ID = "test-project-id";
   private static final GoogleCredentials MOCK_CREDENTIALS = GoogleCredentials.create(null);
+  private static final String AUTH_EMULATOR = "localhost:9099";
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
   @After
   public void tearDown() {
+    TestUtils.unsetEnvironmentVariables(ImmutableSet.of("FIREBASE_AUTH_EMULATOR_HOST"));
     TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
   }
 
@@ -57,8 +62,8 @@ public class FirebaseTokenUtilsTest {
         .setProjectId(TEST_PROJECT_ID)
         .build());
 
-    FirebaseTokenVerifierImpl idTokenVerifier = FirebaseTokenUtils.createIdTokenVerifier(
-        app, CLOCK);
+    FirebaseTokenVerifierImpl idTokenVerifier =
+        FirebaseTokenUtils.createIdTokenVerifier(app, CLOCK);
 
     assertEquals("verifyIdToken()", idTokenVerifier.getMethod());
     assertEquals("ID token", idTokenVerifier.getShortName());
@@ -72,6 +77,26 @@ public class FirebaseTokenUtilsTest {
         "https://securetoken.google.com/test-project-id");
   }
 
+  @Test
+  public void testCreateIdTokenVerifierForEmulator() {
+    TestUtils.setEnvironmentVariables(
+        ImmutableMap.of("FIREBASE_AUTH_EMULATOR_HOST", AUTH_EMULATOR));
+    FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
+        .setCredentials(MOCK_CREDENTIALS)
+        .setProjectId(TEST_PROJECT_ID)
+        .build());
+
+    FirebaseTokenVerifierImpl emulatorVerifier =
+        FirebaseTokenUtils.createIdTokenVerifier(app, CLOCK);
+
+    assertEquals("ID token", emulatorVerifier.getShortName());
+    assertEquals("an ID token", emulatorVerifier.getArticledShortName());
+    assertEquals("https://firebase.google.com/docs/auth/admin/verify-id-tokens",
+        emulatorVerifier.getDocUrl());
+    verifyJwtVerifier(emulatorVerifier.getIdTokenVerifier(),
+        "https://securetoken.google.com/test-project-id");
+  }
+
   @Test
   public void testCreateIdTokenVerifierWithoutProjectId() {
     FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
@@ -89,8 +114,8 @@ public class FirebaseTokenUtilsTest {
         .setProjectId(TEST_PROJECT_ID)
         .build());
 
-    FirebaseTokenVerifierImpl cookieVerifier = FirebaseTokenUtils.createSessionCookieVerifier(
-        app, CLOCK);
+    FirebaseTokenVerifierImpl cookieVerifier =
+        FirebaseTokenUtils.createSessionCookieVerifier(app, CLOCK);
 
     assertEquals("verifySessionCookie()", cookieVerifier.getMethod());
     assertEquals("session cookie", cookieVerifier.getShortName());
@@ -103,6 +128,26 @@ public class FirebaseTokenUtilsTest {
         "https://session.firebase.google.com/test-project-id");
   }
 
+  @Test
+  public void testSessionCookieVerifierForEmulator() {
+    TestUtils.setEnvironmentVariables(
+        ImmutableMap.of("FIREBASE_AUTH_EMULATOR_HOST", AUTH_EMULATOR));
+    FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
+        .setCredentials(MOCK_CREDENTIALS)
+        .setProjectId(TEST_PROJECT_ID)
+        .build());
+
+    FirebaseTokenVerifierImpl emulatorCookieVerifier =
+        FirebaseTokenUtils.createSessionCookieVerifier(app, CLOCK);
+
+    assertEquals("session cookie", emulatorCookieVerifier.getShortName());
+    assertEquals("a session cookie", emulatorCookieVerifier.getArticledShortName());
+    assertEquals("https://firebase.google.com/docs/auth/admin/manage-cookies",
+        emulatorCookieVerifier.getDocUrl());
+    verifyJwtVerifier(emulatorCookieVerifier.getIdTokenVerifier(),
+        "https://session.firebase.google.com/test-project-id");
+  }
+
   @Test
   public void testCreateSessionCookieVerifierWithoutProjectId() {
     FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
diff --git a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java
index 189cb22..d02d2b5 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java
@@ -16,28 +16,28 @@
 
 package com.google.firebase.auth;
 
+import static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.TEST_TOKEN_ISSUER;
+import static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.checkException;
+import static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.checkInvalidTokenException;
+import static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.fullyPopulatedBuilder;
+import static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.newPublicKeysManager;
+import static com.google.firebase.auth.FirebaseTokenVerifierImplTestUtils.newTestTokenVerifier;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-import com.google.api.client.auth.openidconnect.IdTokenVerifier;
 import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
-import com.google.api.client.http.HttpTransport;
 import com.google.api.client.http.LowLevelHttpRequest;
 import com.google.api.client.json.webtoken.JsonWebSignature;
 import com.google.api.client.json.webtoken.JsonWebToken.Payload;
 import com.google.api.client.testing.http.MockHttpTransport;
-import com.google.api.client.testing.http.MockLowLevelHttpResponse;
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.firebase.ErrorCode;
 import com.google.firebase.testing.ServiceAccount;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.util.concurrent.TimeUnit;
-
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -54,8 +54,6 @@ public class FirebaseTokenVerifierImplTest {
           + "-P1wie318In0sInYiOjAsImlhdCI6MTQ4MDk4Mj"
           + "U2NH0.ZWEpoHgIPCAz8Q-cNFBS8jiqClTJ3j27yuRkQo-QxyI";
 
-  private static final String TEST_TOKEN_ISSUER = "https://test.token.issuer";
-
   private FirebaseTokenVerifier tokenVerifier;
   private TestTokenFactory tokenFactory;
 
@@ -83,6 +81,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for missing kid");
     } catch (FirebaseAuthException e) {
       String message = "Firebase test token has no \"kid\" claim. "
           + "See https://test.doc.url for details on how to retrieve a test token.";
@@ -96,6 +95,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for passing custom token");
     } catch (FirebaseAuthException e) {
       String message = "verifyTestToken() expects a test token, but was given a custom token. "
           + "See https://test.doc.url for details on how to retrieve a test token.";
@@ -109,6 +109,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for incorrect alg");
     } catch (FirebaseAuthException e) {
       String message = "Firebase test token has incorrect algorithm. "
           + "Expected \"RS256\" but got \"HSA\". "
@@ -117,12 +118,28 @@ public class FirebaseTokenVerifierImplTest {
     }
   }
 
+  @Test
+  public void testVerifyTokenWithoutAlgorithm() {
+    String token = createTokenWithoutAlgorithm();
+
+    try {
+      tokenVerifier.verifyToken(token);
+      fail("No error thrown for alg: 'none'");
+    } catch (FirebaseAuthException e) {
+      String message = "Firebase test token has incorrect algorithm. "
+          + "Expected \"RS256\" but got \"none\". "
+          + "See https://test.doc.url for details on how to retrieve a test token.";
+      checkInvalidTokenException(e, message);
+    }
+  }
+
   @Test
   public void testVerifyTokenIncorrectAudience() {
     String token = createTokenWithIncorrectAudience();
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for incorrect audience");
     } catch (FirebaseAuthException e) {
       String message = "Firebase test token has incorrect \"aud\" (audience) claim. "
           + "Expected \"proj-test-101\" but got \"invalid-audience\". "
@@ -139,6 +156,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for incorrect issuer");
     } catch (FirebaseAuthException e) {
       String message = "Firebase test token has incorrect \"iss\" (issuer) claim. "
           + "Expected \"https://test.token.issuer\" but got "
@@ -155,6 +173,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for missing subject");
     } catch (FirebaseAuthException e) {
       String message = "Firebase test token has no \"sub\" (subject) claim. "
           + "See https://test.doc.url for details on how to retrieve a test token.";
@@ -168,6 +187,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for empty subject");
     } catch (FirebaseAuthException e) {
       String message = "Firebase test token has an empty string \"sub\" (subject) claim. "
           + "See https://test.doc.url for details on how to retrieve a test token.";
@@ -181,6 +201,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for very long subject");
     } catch (FirebaseAuthException e) {
       String message = "Firebase test token has \"sub\" (subject) claim longer "
           + "than 128 characters. "
@@ -199,6 +220,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for token issued in the future");
     } catch (FirebaseAuthException e) {
       String message = "Firebase test token is not yet valid. "
           + "See https://test.doc.url for details on how to retrieve a test token.";
@@ -216,6 +238,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for expired token");
     } catch (FirebaseAuthException e) {
       String message = "Firebase test token has expired. "
           + "Get a fresh test token and try again. "
@@ -233,6 +256,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for mismatched signature");
     } catch (FirebaseAuthException e) {
       String message = "Failed to verify the signature of Firebase test token. "
           + "See https://test.doc.url for details on how to retrieve a test token.";
@@ -248,6 +272,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for malformed cert");
     } catch (FirebaseAuthException e) {
       String message = "Error while fetching public key certificates: Could not parse certificate";
       assertEquals(ErrorCode.UNKNOWN, e.getErrorCode());
@@ -272,7 +297,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       idTokenVerifier.verifyToken(token);
-      Assert.fail("No exception thrown");
+      fail("No error thrown for failing to fetch certificate");
     } catch (FirebaseAuthException e) {
       String message = "Error while fetching public key certificates: Expected error";
       assertEquals(ErrorCode.UNKNOWN, e.getErrorCode());
@@ -291,6 +316,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       tokenVerifier.verifyToken(token);
+      fail("No error thrown for malformed signature");
     } catch (FirebaseAuthException e) {
       String message = "Failed to verify the signature of Firebase test token. "
           + "See https://test.doc.url for details on how to retrieve a test token.";
@@ -302,6 +328,7 @@ public class FirebaseTokenVerifierImplTest {
   public void testLegacyCustomToken() {
     try {
       tokenVerifier.verifyToken(LEGACY_CUSTOM_TOKEN);
+      fail("No error thrown for passing legacy token");
     } catch (FirebaseAuthException e) {
       String message = "verifyTestToken() expects a test token, but was given a "
           + "legacy custom token. "
@@ -314,6 +341,7 @@ public class FirebaseTokenVerifierImplTest {
   public void testMalformedToken() {
     try {
       tokenVerifier.verifyToken("not.a.jwt");
+      fail("No error thrown for malformed token");
     } catch (FirebaseAuthException e) {
       String message = "Failed to parse Firebase test token. "
           + "Make sure you passed a string that represents a complete and valid JWT. "
@@ -359,6 +387,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       verifier.verifyToken(token);
+      fail("No error thrown for mismatched tenant IDs");
     } catch (FirebaseAuthException e) {
       assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode());
       assertEquals(
@@ -376,6 +405,7 @@ public class FirebaseTokenVerifierImplTest {
 
     try {
       verifier.verifyToken(token);
+      fail("No error thrown for missing tenant ID");
     } catch (FirebaseAuthException e) {
       assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode());
       assertEquals(
@@ -384,35 +414,6 @@ public class FirebaseTokenVerifierImplTest {
     }
   }
 
-  @Test
-  public void testVerifyTokenMissingTenantId() {
-    try {
-      fullyPopulatedBuilder()
-        .setTenantId("TENANT_ID")
-        .build()
-        .verifyToken(tokenFactory.createToken());
-    } catch (FirebaseAuthException e) {
-      assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode());
-      assertEquals(
-          "The tenant ID ('') of the token did not match the expected value ('TENANT_ID')",
-          e.getMessage());
-    }
-  }
-
-  @Test
-  public void testVerifyTokenUnexpectedTenantId() {
-    try {
-      fullyPopulatedBuilder()
-        .build()
-        .verifyToken(createTokenWithTenantId("TENANT_ID"));
-    } catch (FirebaseAuthException e) {
-      assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode());
-      assertEquals(
-          "The tenant ID ('TENANT_ID') of the token did not match the expected value ('')",
-          e.getMessage());
-    }
-  }
-
   @Test(expected = NullPointerException.class)
   public void testBuilderNoPublicKeysManager() {
     fullyPopulatedBuilder().setPublicKeysManager(null).build();
@@ -443,60 +444,12 @@ public class FirebaseTokenVerifierImplTest {
     fullyPopulatedBuilder().setDocUrl(null).build();
   }
 
-  private GooglePublicKeysManager newPublicKeysManager(String certificate) {
-    String serviceAccountCertificates =
-        String.format("{\"%s\" : \"%s\"}", TestTokenFactory.PRIVATE_KEY_ID, certificate);
-    HttpTransport transport = new MockHttpTransport.Builder()
-        .setLowLevelHttpResponse(
-            new MockLowLevelHttpResponse().setContent(serviceAccountCertificates))
-        .build();
-    return newPublicKeysManager(transport);
-  }
-
-  private GooglePublicKeysManager newPublicKeysManager(HttpTransport transport) {
-    return new GooglePublicKeysManager.Builder(
-        transport, FirebaseTokenUtils.UNQUOTED_CTRL_CHAR_JSON_FACTORY)
-        .setClock(TestTokenFactory.CLOCK)
-        .setPublicCertsEncodedUrl("https://test.cert.url")
-        .build();
-  }
-
-  private FirebaseTokenVerifier newTestTokenVerifier(GooglePublicKeysManager publicKeysManager) {
-    return fullyPopulatedBuilder()
-        .setPublicKeysManager(publicKeysManager)
-        .build();
-  }
-
-  private FirebaseTokenVerifierImpl.Builder fullyPopulatedBuilder() {
-    return FirebaseTokenVerifierImpl.builder()
-        .setShortName("test token")
-        .setMethod("verifyTestToken()")
-        .setDocUrl("https://test.doc.url")
-        .setJsonFactory(TestTokenFactory.JSON_FACTORY)
-        .setPublicKeysManager(newPublicKeysManager(ServiceAccount.EDITOR.getCert()))
-        .setInvalidTokenErrorCode(AuthErrorCode.INVALID_ID_TOKEN)
-        .setExpiredTokenErrorCode(AuthErrorCode.EXPIRED_ID_TOKEN)
-        .setIdTokenVerifier(newIdTokenVerifier());
-  }
-
-  private IdTokenVerifier newIdTokenVerifier() {
-    return new IdTokenVerifier.Builder()
-          .setClock(TestTokenFactory.CLOCK)
-          .setAudience(ImmutableList.of(TestTokenFactory.PROJECT_ID))
-          .setIssuer(TEST_TOKEN_ISSUER)
-          .build();
-  }
-
   private String createTokenWithoutKeyId() {
-    JsonWebSignature.Header header = tokenFactory.createHeader();
-    header.setKeyId(null);
-    return tokenFactory.createToken(header);
+    return tokenFactory.createTokenWithoutKeyId(false);
   }
 
   private String createTokenWithSubject(String sub) {
-    Payload payload = tokenFactory.createTokenPayload();
-    payload.setSubject(sub);
-    return tokenFactory.createToken(payload);
+    return tokenFactory.createTokenWithSubject(sub, false);
   }
 
   private String createCustomToken() {
@@ -508,45 +461,26 @@ public class FirebaseTokenVerifierImplTest {
   }
 
   private String createTokenWithIncorrectAlgorithm() {
-    JsonWebSignature.Header header = tokenFactory.createHeader();
-    header.setAlgorithm("HSA");
-    return tokenFactory.createToken(header);
+    return tokenFactory.createTokenWithIncorrectAlgorithm(false);
+  }
+
+  private String createTokenWithoutAlgorithm() {
+    return tokenFactory.createTokenWithoutAlgorithm(false);
   }
 
   private String createTokenWithIncorrectAudience() {
-    Payload payload = tokenFactory.createTokenPayload();
-    payload.setAudience("invalid-audience");
-    return tokenFactory.createToken(payload);
+    return tokenFactory.createTokenWithIncorrectAudience(false);
   }
 
   private String createTokenWithIncorrectIssuer() {
-    Payload payload = tokenFactory.createTokenPayload();
-    payload.setIssuer("https://incorrect.issuer.prefix/" + TestTokenFactory.PROJECT_ID);
-    return tokenFactory.createToken(payload);
+    return tokenFactory.createTokenWithIncorrectIssuer(false);
   }
 
   private String createTokenWithTimestamps(long issuedAtSeconds, long expirationSeconds) {
-    Payload payload = tokenFactory.createTokenPayload();
-    payload.setIssuedAtTimeSeconds(issuedAtSeconds);
-    payload.setExpirationTimeSeconds(expirationSeconds);
-    return tokenFactory.createToken(payload);
-  }
-
-  private void checkInvalidTokenException(FirebaseAuthException e, String message) {
-    checkException(e, message, AuthErrorCode.INVALID_ID_TOKEN);
-  }
-
-  private void checkException(FirebaseAuthException e, String message, AuthErrorCode errorCode) {
-    assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
-    assertEquals(message, e.getMessage());
-    assertNull(e.getCause());
-    assertNull(e.getHttpResponse());
-    assertEquals(errorCode, e.getAuthErrorCode());
+    return tokenFactory.createTokenWithTimestamps(issuedAtSeconds, expirationSeconds, false);
   }
 
   private String createTokenWithTenantId(String tenantId) {
-    Payload payload = tokenFactory.createTokenPayload();
-    payload.set("firebase", ImmutableMap.of("tenant", tenantId));
-    return tokenFactory.createToken(payload);
+    return tokenFactory.createTokenWithTenantId(tenantId, false);
   }
 }
diff --git a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTestUtils.java b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTestUtils.java
new file mode 100644
index 0000000..5f23315
--- /dev/null
+++ b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTestUtils.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.google.api.client.auth.openidconnect.IdTokenVerifier;
+import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.testing.http.MockHttpTransport;
+import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import com.google.common.collect.ImmutableList;
+import com.google.firebase.ErrorCode;
+import com.google.firebase.testing.ServiceAccount;
+
+class FirebaseTokenVerifierImplTestUtils {
+  public static final String TEST_TOKEN_ISSUER = "https://test.token.issuer";
+
+  static void checkInvalidTokenException(FirebaseAuthException e, String message) {
+    checkException(e, message, AuthErrorCode.INVALID_ID_TOKEN);
+  }
+
+  static void checkException(FirebaseAuthException e, String message, AuthErrorCode errorCode) {
+    assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
+    assertEquals(message, e.getMessage());
+    assertNull(e.getCause());
+    assertNull(e.getHttpResponse());
+    assertEquals(errorCode, e.getAuthErrorCode());
+  }
+
+  static GooglePublicKeysManager newPublicKeysManager(String certificate) {
+    String serviceAccountCertificates =
+        String.format("{\"%s\" : \"%s\"}", TestTokenFactory.PRIVATE_KEY_ID, certificate);
+    HttpTransport transport = new MockHttpTransport.Builder()
+        .setLowLevelHttpResponse(
+            new MockLowLevelHttpResponse().setContent(serviceAccountCertificates))
+        .build();
+    return newPublicKeysManager(transport);
+  }
+
+  static GooglePublicKeysManager newPublicKeysManager(HttpTransport transport) {
+    return new GooglePublicKeysManager.Builder(
+        transport, FirebaseTokenUtils.UNQUOTED_CTRL_CHAR_JSON_FACTORY)
+        .setClock(TestTokenFactory.CLOCK)
+        .setPublicCertsEncodedUrl("https://test.cert.url")
+        .build();
+  }
+
+  static FirebaseTokenVerifier newTestTokenVerifier(GooglePublicKeysManager publicKeysManager) {
+    return fullyPopulatedBuilder()
+        .setPublicKeysManager(publicKeysManager)
+        .build();
+  }
+
+  static FirebaseTokenVerifierImpl.Builder fullyPopulatedBuilder() {
+    return FirebaseTokenVerifierImpl.builder()
+        .setShortName("test token")
+        .setMethod("verifyTestToken()")
+        .setDocUrl("https://test.doc.url")
+        .setJsonFactory(TestTokenFactory.JSON_FACTORY)
+        .setPublicKeysManager(newPublicKeysManager(ServiceAccount.EDITOR.getCert()))
+        .setInvalidTokenErrorCode(AuthErrorCode.INVALID_ID_TOKEN)
+        .setExpiredTokenErrorCode(AuthErrorCode.EXPIRED_ID_TOKEN)
+        .setIdTokenVerifier(newIdTokenVerifier());
+  }
+
+  static IdTokenVerifier newIdTokenVerifier() {
+    return new IdTokenVerifier.Builder()
+        .setClock(TestTokenFactory.CLOCK)
+        .setAudience(ImmutableList.of(TestTokenFactory.PROJECT_ID))
+        .setIssuer(TEST_TOKEN_ISSUER)
+        .build();
+  }
+}
diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
index 772f9ba..397b91f 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
@@ -39,6 +39,7 @@ import com.google.common.base.Strings;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.firebase.ErrorCode;
 import com.google.firebase.FirebaseApp;
@@ -90,12 +91,17 @@ public class FirebaseUserManagerTest {
 
   private static final String TENANTS_BASE_URL = PROJECT_BASE_URL + "/tenants";
 
+  private static final String AUTH_EMULATOR = "localhost:8000";
+  private static final String PROJECT_BASE_URL_EMULATOR =
+          "http://" + AUTH_EMULATOR + "/identitytoolkit.googleapis.com/v2/projects/test-project-id";
+
   private static final String SAML_RESPONSE = TestUtils.loadResource("saml.json");
 
   private static final String OIDC_RESPONSE = TestUtils.loadResource("oidc.json");
 
   @After
   public void tearDown() {
+    TestUtils.unsetEnvironmentVariables(ImmutableSet.of("FIREBASE_AUTH_EMULATOR_HOST"));
     TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
   }
 
@@ -1490,7 +1496,10 @@ public class FirebaseUserManagerTest {
             .setDisplayName("DISPLAY_NAME")
             .setEnabled(true)
             .setClientId("CLIENT_ID")
-            .setIssuer("https://oidc.com/issuer");
+            .setClientSecret("CLIENT_SECRET")
+            .setIssuer("https://oidc.com/issuer")
+            .setCodeResponseType(true)
+            .setIdTokenResponseType(true);
 
     OidcProviderConfig config = FirebaseAuth.getInstance().createOidcProviderConfig(createRequest);
 
@@ -1501,7 +1510,13 @@ public class FirebaseUserManagerTest {
     assertEquals("DISPLAY_NAME", parsed.get("displayName"));
     assertTrue((boolean) parsed.get("enabled"));
     assertEquals("CLIENT_ID", parsed.get("clientId"));
+    assertEquals("CLIENT_SECRET", parsed.get("clientSecret"));
     assertEquals("https://oidc.com/issuer", parsed.get("issuer"));
+
+    Map<String, Boolean> responseType = (Map<String, Boolean>) parsed.get("responseType");
+    assertTrue(responseType.get("code"));
+    assertTrue(responseType.get("idToken"));
+
     GenericUrl url = interceptor.getResponse().getRequest().getUrl();
     assertEquals("oidc.provider-id", url.getFirst("oauthIdpConfigId"));
   }
@@ -1515,9 +1530,12 @@ public class FirebaseUserManagerTest {
             .setDisplayName("DISPLAY_NAME")
             .setEnabled(true)
             .setClientId("CLIENT_ID")
-            .setIssuer("https://oidc.com/issuer");
+            .setClientSecret("CLIENT_SECRET")
+            .setIssuer("https://oidc.com/issuer")
+            .setCodeResponseType(true)
+            .setIdTokenResponseType(true);
 
-    OidcProviderConfig config = 
+    OidcProviderConfig config =
         FirebaseAuth.getInstance().createOidcProviderConfigAsync(createRequest).get();
 
     checkOidcProviderConfig(config, "oidc.provider-id");
@@ -1527,7 +1545,13 @@ public class FirebaseUserManagerTest {
     assertEquals("DISPLAY_NAME", parsed.get("displayName"));
     assertTrue((boolean) parsed.get("enabled"));
     assertEquals("CLIENT_ID", parsed.get("clientId"));
+    assertEquals("CLIENT_SECRET", parsed.get("clientSecret"));
     assertEquals("https://oidc.com/issuer", parsed.get("issuer"));
+
+    Map<String, Boolean> responseType = (Map<String, Boolean>) parsed.get("responseType");
+    assertTrue(responseType.get("code"));
+    assertTrue(responseType.get("idToken"));
+
     GenericUrl url = interceptor.getResponse().getRequest().getUrl();
     assertEquals("oidc.provider-id", url.getFirst("oauthIdpConfigId"));
   }
@@ -1730,7 +1754,10 @@ public class FirebaseUserManagerTest {
             .setDisplayName("DISPLAY_NAME")
             .setEnabled(true)
             .setClientId("CLIENT_ID")
-            .setIssuer("https://oidc.com/issuer");
+            .setClientSecret("CLIENT_SECRET")
+            .setIssuer("https://oidc.com/issuer")
+            .setCodeResponseType(true)
+            .setIdTokenResponseType(true);
 
     OidcProviderConfig config = tenantAwareAuth.updateOidcProviderConfig(request);
 
@@ -1739,12 +1766,18 @@ public class FirebaseUserManagerTest {
     String expectedUrl = TENANTS_BASE_URL + "/TENANT_ID/oauthIdpConfigs/oidc.provider-id";
     checkUrl(interceptor, "PATCH", expectedUrl);
     GenericUrl url = interceptor.getResponse().getRequest().getUrl();
-    assertEquals("clientId,displayName,enabled,issuer", url.getFirst("updateMask"));
+    assertEquals("clientId,clientSecret,displayName,enabled,issuer,responseType.code,"
+        + "responseType.idToken", url.getFirst("updateMask"));
     GenericJson parsed = parseRequestContent(interceptor);
     assertEquals("DISPLAY_NAME", parsed.get("displayName"));
     assertTrue((boolean) parsed.get("enabled"));
     assertEquals("CLIENT_ID", parsed.get("clientId"));
+    assertEquals("CLIENT_SECRET", parsed.get("clientSecret"));
     assertEquals("https://oidc.com/issuer", parsed.get("issuer"));
+
+    Map<String, Boolean> responseType = (Map<String, Boolean>) parsed.get("responseType");
+    assertTrue(responseType.get("code"));
+    assertTrue(responseType.get("idToken"));
   }
 
   @Test
@@ -2678,6 +2711,33 @@ public class FirebaseUserManagerTest {
     checkUrl(interceptor, "DELETE", expectedUrl);
   }
 
+  @Test
+  public void testCreateOidcProviderFromEmulatorAuth() throws Exception {
+    TestUtils.setEnvironmentVariables(
+            ImmutableMap.of("FIREBASE_AUTH_EMULATOR_HOST", AUTH_EMULATOR));
+    TestResponseInterceptor interceptor = initializeAppForUserManagement(OIDC_RESPONSE);
+    OidcProviderConfig.CreateRequest createRequest =
+            new OidcProviderConfig.CreateRequest()
+                    .setProviderId("oidc.provider-id")
+                    .setDisplayName("DISPLAY_NAME")
+                    .setEnabled(true)
+                    .setClientId("CLIENT_ID")
+                    .setIssuer("https://oidc.com/issuer");
+
+    OidcProviderConfig config = FirebaseAuth.getInstance().createOidcProviderConfig(createRequest);
+
+    checkOidcProviderConfig(config, "oidc.provider-id");
+    checkRequestHeaders(interceptor);
+    checkUrl(interceptor, "POST", PROJECT_BASE_URL_EMULATOR + "/oauthIdpConfigs");
+    GenericJson parsed = parseRequestContent(interceptor);
+    assertEquals("DISPLAY_NAME", parsed.get("displayName"));
+    assertTrue((boolean) parsed.get("enabled"));
+    assertEquals("CLIENT_ID", parsed.get("clientId"));
+    assertEquals("https://oidc.com/issuer", parsed.get("issuer"));
+    GenericUrl url = interceptor.getResponse().getRequest().getUrl();
+    assertEquals("oidc.provider-id", url.getFirst("oauthIdpConfigId"));
+  }
+
   private static TestResponseInterceptor initializeAppForUserManagementWithStatusCode(
       int statusCode, String response) {
     FirebaseApp.initializeApp(FirebaseOptions.builder()
@@ -2792,7 +2852,10 @@ public class FirebaseUserManagerTest {
     assertEquals("DISPLAY_NAME", config.getDisplayName());
     assertTrue(config.isEnabled());
     assertEquals("CLIENT_ID", config.getClientId());
+    assertEquals("CLIENT_SECRET", config.getClientSecret());
     assertEquals("https://oidc.com/issuer", config.getIssuer());
+    assertTrue(config.isCodeResponseType());
+    assertFalse(config.isIdTokenResponseType());
   }
 
   private static void checkSamlProviderConfig(SamlProviderConfig config, String providerId) {
@@ -2824,5 +2887,4 @@ public class FirebaseUserManagerTest {
   private interface UserManagerOp {
     void call(FirebaseAuth auth) throws Exception;
   }
-
 }
diff --git a/src/test/java/com/google/firebase/auth/OidcProviderConfigTest.java b/src/test/java/com/google/firebase/auth/OidcProviderConfigTest.java
index 1fb4ca3..b8a5d50 100644
--- a/src/test/java/com/google/firebase/auth/OidcProviderConfigTest.java
+++ b/src/test/java/com/google/firebase/auth/OidcProviderConfigTest.java
@@ -18,11 +18,13 @@ package com.google.firebase.auth;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import com.google.api.client.googleapis.util.Utils;
 import com.google.api.client.json.JsonFactory;
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.Map;
 import org.junit.Test;
 
@@ -36,7 +38,12 @@ public class OidcProviderConfigTest {
         + "  'displayName': 'DISPLAY_NAME',"
         + "  'enabled':      true,"
         + "  'clientId':    'CLIENT_ID',"
-        + "  'issuer':      'https://oidc.com/issuer'"
+        + "  'clientSecret':'CLIENT_SECRET',"
+        + "  'issuer':      'https://oidc.com/issuer',"
+        + "  'responseType': {"
+        + "    'code':  true,"
+        + "    'idToken': false"
+        + "   }"
         + "}").replace("'", "\"");
 
   @Test
@@ -47,7 +54,35 @@ public class OidcProviderConfigTest {
     assertEquals("DISPLAY_NAME", config.getDisplayName());
     assertTrue(config.isEnabled());
     assertEquals("CLIENT_ID", config.getClientId());
+    assertEquals("CLIENT_SECRET", config.getClientSecret());
     assertEquals("https://oidc.com/issuer", config.getIssuer());
+    assertTrue(config.isCodeResponseType());
+    assertFalse(config.isIdTokenResponseType());
+  }
+
+  @Test
+  public void testEnsureResponseType() {
+    Map<String, Object> properties = new HashMap<>();
+
+    Map<String, Boolean> responseType = OidcProviderConfig.ensureResponseType(properties);
+
+    assertNotNull(responseType);
+    assertEquals(responseType, properties.get("responseType"));
+  }
+
+  @Test
+  public void testEnsureResponseTypeAlreadyPresent() {
+    Map<String, Object> properties = new HashMap<>();
+    Map<String, Boolean> responseType = new HashMap<>();
+    responseType.put("code", true);
+    properties.put("responseType", responseType);
+
+    Map<String, Boolean> returnedResponseType = OidcProviderConfig.ensureResponseType(properties);
+
+    assertEquals(responseType, returnedResponseType);
+    assertTrue(returnedResponseType.get("code"));
+    assertEquals(returnedResponseType.size(), 1);
+    assertEquals(responseType, properties.get("responseType"));
   }
 
   @Test
@@ -58,15 +93,23 @@ public class OidcProviderConfigTest {
       .setDisplayName("DISPLAY_NAME")
       .setEnabled(false)
       .setClientId("CLIENT_ID")
-      .setIssuer("https://oidc.com/issuer");
+      .setClientSecret("CLIENT_SECRET")
+      .setIssuer("https://oidc.com/issuer")
+      .setCodeResponseType(true)
+      .setIdTokenResponseType(false);
 
     assertEquals("oidc.provider-id", createRequest.getProviderId());
     Map<String,Object> properties = createRequest.getProperties();
-    assertEquals(properties.size(), 4);
+    assertEquals(properties.size(), 6);
     assertEquals("DISPLAY_NAME", (String) properties.get("displayName"));
     assertFalse((boolean) properties.get("enabled"));
     assertEquals("CLIENT_ID", (String) properties.get("clientId"));
+    assertEquals("CLIENT_SECRET", properties.get("clientSecret"));
     assertEquals("https://oidc.com/issuer", (String) properties.get("issuer"));
+
+    Map<String, Boolean> responseType = (Map<String, Boolean>) properties.get("responseType");
+    assertTrue(responseType.get("code"));
+    assertFalse(responseType.get("idToken"));
   }
 
   @Test(expected = IllegalArgumentException.class)
@@ -99,6 +142,16 @@ public class OidcProviderConfigTest {
     new OidcProviderConfig.CreateRequest().setIssuer("not a valid url");
   }
 
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestMissingClientSecret() {
+    new OidcProviderConfig.CreateRequest().setClientSecret(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestEmptyClientSecret() {
+    new OidcProviderConfig.CreateRequest().setClientSecret("");
+  }
+
   @Test
   public void testUpdateRequestFromOidcProviderConfig() throws IOException {
     OidcProviderConfig config = jsonFactory.fromString(OIDC_JSON_STRING, OidcProviderConfig.class);
@@ -117,15 +170,23 @@ public class OidcProviderConfigTest {
       .setDisplayName("DISPLAY_NAME")
       .setEnabled(false)
       .setClientId("CLIENT_ID")
-      .setIssuer("https://oidc.com/issuer");
+      .setClientSecret("CLIENT_SECRET")
+      .setIssuer("https://oidc.com/issuer")
+      .setCodeResponseType(true)
+      .setIdTokenResponseType(false);
 
     assertEquals("oidc.provider-id", updateRequest.getProviderId());
     Map<String,Object> properties = updateRequest.getProperties();
-    assertEquals(properties.size(), 4);
+    assertEquals(properties.size(), 6);
     assertEquals("DISPLAY_NAME", (String) properties.get("displayName"));
     assertFalse((boolean) properties.get("enabled"));
     assertEquals("CLIENT_ID", (String) properties.get("clientId"));
+    assertEquals("CLIENT_SECRET", (String) properties.get("clientSecret"));
     assertEquals("https://oidc.com/issuer", (String) properties.get("issuer"));
+
+    Map<String, Boolean> responseType = (Map<String, Boolean>) properties.get("responseType");
+    assertTrue(responseType.get("code"));
+    assertFalse(responseType.get("idToken"));
   }
 
   @Test(expected = IllegalArgumentException.class)
@@ -157,4 +218,14 @@ public class OidcProviderConfigTest {
   public void testUpdateRequestInvalidIssuerUrl() {
     new OidcProviderConfig.UpdateRequest("oidc.provider-id").setIssuer("not a valid url");
   }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testUpdateRequestMissingClientSecret() {
+    new OidcProviderConfig.UpdateRequest("oidc.provider-id").setClientSecret(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testUpdateRequestEmptyClientSecret() {
+    new OidcProviderConfig.UpdateRequest("oidc.provider-id").setClientSecret("");
+  }
 }
diff --git a/src/test/java/com/google/firebase/auth/TestTokenFactory.java b/src/test/java/com/google/firebase/auth/TestTokenFactory.java
index 1f774ee..0510dbb 100644
--- a/src/test/java/com/google/firebase/auth/TestTokenFactory.java
+++ b/src/test/java/com/google/firebase/auth/TestTokenFactory.java
@@ -20,8 +20,11 @@ import com.google.api.client.googleapis.util.Utils;
 import com.google.api.client.json.JsonFactory;
 import com.google.api.client.json.webtoken.JsonWebSignature;
 import com.google.api.client.json.webtoken.JsonWebToken;
+import com.google.api.client.json.webtoken.JsonWebToken.Payload;
 import com.google.api.client.testing.http.FixedClock;
+import com.google.api.client.util.Base64;
 import com.google.api.client.util.Clock;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.io.BaseEncoding;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
@@ -69,6 +72,30 @@ class TestTokenFactory {
     }
   }
 
+  public String createUnsignedTokenForEmulator() {
+    return createUnsignedTokenForEmulator(createHeaderForEmulator(), createTokenPayload());
+  }
+
+  public String createUnsignedTokenForEmulator(JsonWebSignature.Header header) {
+    return createUnsignedTokenForEmulator(header, createTokenPayload());
+  }
+
+  public String createUnsignedTokenForEmulator(JsonWebToken.Payload payload) {
+    return createUnsignedTokenForEmulator(createHeaderForEmulator(), payload);
+  }
+
+  public String createUnsignedTokenForEmulator(
+      JsonWebSignature.Header header, JsonWebSignature.Payload payload) {
+    try {
+      String encodedHeader = Base64.encodeBase64URLSafeString(JSON_FACTORY.toByteArray(header));
+      String encodedPayload = Base64.encodeBase64URLSafeString(JSON_FACTORY.toByteArray(payload));
+      // Unsigned token with no signature component
+      return String.format("%s.%s.", encodedHeader, encodedPayload);
+    } catch (IOException e) {
+      throw new RuntimeException("Failed to create test token", e);
+    }
+  }
+
   public JsonWebSignature.Header createHeader() {
     JsonWebSignature.Header header = new JsonWebSignature.Header();
     header.setAlgorithm("RS256");
@@ -86,4 +113,69 @@ class TestTokenFactory {
     payload.setSubject(UID);
     return payload;
   }
+
+  public JsonWebSignature.Header createHeaderForEmulator() {
+    JsonWebSignature.Header header = new JsonWebSignature.Header();
+    header.setAlgorithm("none");
+    header.setType("JWT");
+    return header;
+  }
+
+  public String createTokenWithoutKeyId(boolean isEmulatorMode) {
+    JsonWebSignature.Header header = createHeader();
+    header.setKeyId(null);
+    return isEmulatorMode ? createUnsignedTokenForEmulator(header)
+        : createToken(header);
+  }
+
+  public String createTokenWithSubject(String sub, boolean isEmulatorMode) {
+    Payload payload = createTokenPayload();
+    payload.setSubject(sub);
+    return isEmulatorMode ? createUnsignedTokenForEmulator(payload)
+        : createToken(payload);
+  }
+
+  public String createTokenWithIncorrectAlgorithm(boolean isEmulatorMode) {
+    JsonWebSignature.Header header = createHeader();
+    header.setAlgorithm("HSA");
+    return isEmulatorMode ? createUnsignedTokenForEmulator(header)
+        : createToken(header);
+  }
+
+  public String createTokenWithoutAlgorithm(boolean isEmulatorMode) {
+    JsonWebSignature.Header header = createHeader();
+    header.setAlgorithm("none");
+    return isEmulatorMode ? createUnsignedTokenForEmulator(header)
+        : createToken(header);
+  }
+
+  public String createTokenWithIncorrectAudience(boolean isEmulatorMode) {
+    Payload payload = createTokenPayload();
+    payload.setAudience("invalid-audience");
+    return isEmulatorMode ? createUnsignedTokenForEmulator(payload)
+        : createToken(payload);
+  }
+
+  public String createTokenWithIncorrectIssuer(boolean isEmulatorMode) {
+    Payload payload = createTokenPayload();
+    payload.setIssuer("https://incorrect.issuer.prefix/" + TestTokenFactory.PROJECT_ID);
+    return isEmulatorMode ? createUnsignedTokenForEmulator(payload)
+        : createToken(payload);
+  }
+
+  public String createTokenWithTimestamps(long issuedAtSeconds, long expirationSeconds,
+      boolean isEmulatorMode) {
+    Payload payload = createTokenPayload();
+    payload.setIssuedAtTimeSeconds(issuedAtSeconds);
+    payload.setExpirationTimeSeconds(expirationSeconds);
+    return isEmulatorMode ? createUnsignedTokenForEmulator(payload)
+        : createToken(payload);
+  }
+
+  public String createTokenWithTenantId(String tenantId, boolean isEmulatorMode) {
+    Payload payload = createTokenPayload();
+    payload.set("firebase", ImmutableMap.of("tenant", tenantId));
+    return isEmulatorMode ? createUnsignedTokenForEmulator(payload)
+        : createToken(payload);
+  }
 }
diff --git a/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java b/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java
index fee4d8f..73477ad 100644
--- a/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java
+++ b/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java
@@ -219,6 +219,14 @@ public class CryptoSignersTest {
     assertArrayEquals("local-signed-bytes".getBytes(), data);
   }
 
+  @Test
+  public void testEmulatorCryptoSigner() throws Exception {
+    CryptoSigner signer = new CryptoSigners.EmulatorCryptoSigner();
+    byte[] data = signer.sign("foo".getBytes());
+    // No signed bytes returned
+    assertEquals(data.length, 0);
+  }
+
   @After
   public void tearDown() {
     TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
diff --git a/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java b/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java
index 4bc84cb..c760465 100644
--- a/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java
+++ b/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java
@@ -35,6 +35,8 @@ 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.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.firebase.ErrorCode;
 import com.google.firebase.FirebaseApp;
@@ -52,6 +54,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+
 import org.junit.After;
 import org.junit.Test;
 
@@ -68,8 +71,14 @@ public class FirebaseTenantClientTest {
 
   private static final String TENANTS_BASE_URL = PROJECT_BASE_URL + "/tenants";
 
+  private static final String AUTH_EMULATOR = "localhost:8000";
+  private static final String PROJECT_BASE_URL_EMULATOR =
+          "http://" + AUTH_EMULATOR + "/identitytoolkit.googleapis.com/v2/projects/test-project-id";
+  private static final String TENANTS_BASE_URL_EMULATOR = PROJECT_BASE_URL_EMULATOR + "/tenants";
+
   @After
-  public void tearDown() {
+  public void tearDown() throws ReflectiveOperationException {
+    TestUtils.unsetEnvironmentVariables(ImmutableSet.of("FIREBASE_AUTH_EMULATOR_HOST"));
     TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
   }
 
@@ -312,6 +321,20 @@ public class FirebaseTenantClientTest {
     checkUrl(interceptor, "DELETE", TENANTS_BASE_URL + "/UNKNOWN");
   }
 
+  @Test
+  public void testGetTenantFromAuthEmulator() throws Exception {
+    TestUtils.setEnvironmentVariables(
+            ImmutableMap.of("FIREBASE_AUTH_EMULATOR_HOST", AUTH_EMULATOR));
+    TestResponseInterceptor interceptor = initializeAppForTenantManagement(
+            TestUtils.loadResource("tenant.json"));
+
+    Tenant tenant = FirebaseAuth.getInstance().getTenantManager().getTenant("TENANT_1");
+
+    checkTenant(tenant, "TENANT_1");
+    checkRequestHeaders(interceptor);
+    checkUrl(interceptor, "GET", TENANTS_BASE_URL_EMULATOR + "/TENANT_1");
+  }
+
   private static void checkTenant(Tenant tenant, String tenantId) {
     assertEquals(tenantId, tenant.getTenantId());
     assertEquals("DISPLAY_NAME", tenant.getDisplayName());
diff --git a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java
index 1ebac21..3c4578e 100644
--- a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java
+++ b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java
@@ -279,18 +279,28 @@ public class TenantAwareFirebaseAuthIT {
               .setDisplayName("DisplayName")
               .setEnabled(true)
               .setClientId("ClientId")
-              .setIssuer("https://oidc.com/issuer"));
+              .setClientSecret("ClientSecret")
+              .setIssuer("https://oidc.com/issuer")
+              .setCodeResponseType(true)
+              .setIdTokenResponseType(false));
+
     assertEquals(providerId, config.getProviderId());
     assertEquals("DisplayName", config.getDisplayName());
     assertEquals("ClientId", config.getClientId());
+    assertEquals("ClientSecret", config.getClientSecret());
     assertEquals("https://oidc.com/issuer", config.getIssuer());
+    assertTrue(config.isCodeResponseType());
+    assertFalse(config.isIdTokenResponseType());
 
     // Get provider config
     config = tenantAwareAuth.getOidcProviderConfigAsync(providerId).get();
     assertEquals(providerId, config.getProviderId());
     assertEquals("DisplayName", config.getDisplayName());
     assertEquals("ClientId", config.getClientId());
+    assertEquals("ClientSecret", config.getClientSecret());
     assertEquals("https://oidc.com/issuer", config.getIssuer());
+    assertTrue(config.isCodeResponseType());
+    assertFalse(config.isIdTokenResponseType());
 
     // Update provider config
     OidcProviderConfig.UpdateRequest updateRequest =
@@ -298,13 +308,20 @@ public class TenantAwareFirebaseAuthIT {
             .setDisplayName("NewDisplayName")
             .setEnabled(false)
             .setClientId("NewClientId")
-            .setIssuer("https://oidc.com/new-issuer");
+            .setClientSecret("NewClientSecret")
+            .setIssuer("https://oidc.com/new-issuer")
+            .setCodeResponseType(false)
+            .setIdTokenResponseType(true);
+
     config = tenantAwareAuth.updateOidcProviderConfigAsync(updateRequest).get();
     assertEquals(providerId, config.getProviderId());
     assertEquals("NewDisplayName", config.getDisplayName());
     assertFalse(config.isEnabled());
     assertEquals("NewClientId", config.getClientId());
+    assertEquals("NewClientSecret", config.getClientSecret());
     assertEquals("https://oidc.com/new-issuer", config.getIssuer());
+    assertFalse(config.isCodeResponseType());
+    assertTrue(config.isIdTokenResponseType());
 
     // Delete provider config
     temporaryProviderConfig.deleteOidcProviderConfig(providerId);
diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIT.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIT.java
index bb90c6c..54b7438 100644
--- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIT.java
+++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIT.java
@@ -53,6 +53,11 @@ public class FirebaseRemoteConfigIT {
     remoteConfig = FirebaseRemoteConfig.getInstance(IntegrationTestUtils.ensureDefaultApp());
   }
 
+  @Test
+  public void testToTriggerNightlyEmailNotifications() {
+    assertEquals("a", "b");
+  }
+
   @Test
   public void testValidateTemplate() throws FirebaseRemoteConfigException {
     final Template inputTemplate = remoteConfig.getTemplate();
diff --git a/src/test/resources/listOidc.json b/src/test/resources/listOidc.json
index 0c13ea4..82a2f76 100644
--- a/src/test/resources/listOidc.json
+++ b/src/test/resources/listOidc.json
@@ -4,12 +4,22 @@
     "displayName" : "DISPLAY_NAME",
     "enabled" : true,
     "clientId" : "CLIENT_ID",
-    "issuer" : "https://oidc.com/issuer"
+    "clientSecret" : "CLIENT_SECRET",
+    "issuer" : "https://oidc.com/issuer",
+    "responseType" : {
+      "code": true,
+      "idToken": false
+    }
   }, {
     "name": "projects/projectId/oauthIdpConfigs/oidc.provider-id2",
     "displayName" : "DISPLAY_NAME",
     "enabled" : true,
     "clientId" : "CLIENT_ID",
-    "issuer" : "https://oidc.com/issuer"
+    "clientSecret" : "CLIENT_SECRET",
+    "issuer" : "https://oidc.com/issuer",
+    "responseType" : {
+      "code": true,
+      "idToken": false
+    }
   } ]
 }
diff --git a/src/test/resources/oidc.json b/src/test/resources/oidc.json
index e2f1845..0aed0df 100644
--- a/src/test/resources/oidc.json
+++ b/src/test/resources/oidc.json
@@ -3,5 +3,10 @@
   "displayName" : "DISPLAY_NAME",
   "enabled" : true,
   "clientId" : "CLIENT_ID",
-  "issuer" : "https://oidc.com/issuer"
+  "clientSecret" : "CLIENT_SECRET",
+  "issuer" : "https://oidc.com/issuer",
+  "responseType" : {
+    "code": true,
+    "idToken": false
+  }
 }