Repository: firebase/firebase-admin-java (java)

Issue:
Global VerifyIdToken() fails when token has a tenantId
  * Operating System version: Windows 10 2004
  * Firebase SDK version: 7.0.0
  * Library version: Not sure
  * Firebase Product: Auth (auth, database, storage, etc)

When a tenant Firebase JWT token is verified using the FirebaseAuth.verifyIdToken() this fails. This is in direct conflict to the google documentation for Google Identity Platform docs (https://cloud.google.com/identity-platform/docs/multi-tenancy-managing-tenants#identity_verification).

The exception message thrown is: "com.google.firebase.auth.FirebaseAuthException: The tenant ID ($MY_TENANT_ID) of the token did not match the expected value ('')"

It's worth noting this does not occur using the Node.JS version of the Firebase Admin SDK.

To reproduce Try and verify a Firebase JWT with a tenant with the global FirebaseAuth.verifyIdToken() method.

The issue appears to be that in the FirebaseTokenVerifierImp class it sets the tenant Id in the constructor as an empty string (makes sense there is no tenant here). However in the verifyToken() method this calls the checkTenantId() method. This compares the empty tenant Id to the tenant id in the token which throws the exception.

It probably makes sense for the TenantAwareFirebaseAuth class to check this in the verify token but not in the base class (i.e. the same as the Node version). Otherwise you will need to:
* decode the token first to get the tenant Id
* create a TenantAwareFirebaseAuth instance
* verify the token.

Pull request description:
fix(auth): Support verifying tenant ID tokens in FirebaseAuth
`FirebaseAuth` should be able to verify ID tokens regardless of the tenant ID.

Resolves #474 

Patch:
diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
index 273ec15..bbe741f 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
@@ -71,7 +71,7 @@ final class FirebaseTokenVerifierImpl implements FirebaseTokenVerifier {
     this.docUrl = builder.docUrl;
     this.invalidTokenErrorCode = checkNotNull(builder.invalidTokenErrorCode);
     this.expiredTokenErrorCode = checkNotNull(builder.expiredTokenErrorCode);
-    this.tenantId = Strings.nullToEmpty(builder.tenantId);
+    this.tenantId = builder.tenantId;
   }
 
   /**
@@ -323,11 +323,11 @@ final class FirebaseTokenVerifierImpl implements FirebaseTokenVerifier {
   }
 
   private void checkTenantId(final FirebaseToken firebaseToken) throws FirebaseAuthException {
-    String tokenTenantId = Strings.nullToEmpty(firebaseToken.getTenantId());
-    if (!this.tenantId.equals(tokenTenantId)) {
+    String tokenTenantId = firebaseToken.getTenantId();
+    if (this.tenantId != null && !this.tenantId.equals(tokenTenantId)) {
       String message = String.format(
           "The tenant ID ('%s') of the token did not match the expected value ('%s')",
-          tokenTenantId,
+          Strings.nullToEmpty(tokenTenantId),
           tenantId);
       throw newException(message, AuthErrorCode.TENANT_ID_MISMATCH);
     }


Test patch:
diff --git a/src/test/java/com/google/firebase/FirebaseAppTest.java b/src/test/java/com/google/firebase/FirebaseAppTest.java
index d481e3c..26ca934 100644
--- a/src/test/java/com/google/firebase/FirebaseAppTest.java
+++ b/src/test/java/com/google/firebase/FirebaseAppTest.java
@@ -66,7 +66,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mockito;
 
-/** 
+/**
  * Unit tests for {@link com.google.firebase.FirebaseApp}.
  */
 public class FirebaseAppTest {
@@ -472,8 +472,8 @@ public class FirebaseAppTest {
   public void testEmptyFirebaseConfigFile() {
     setFirebaseConfigEnvironmentVariable("firebase_config_empty.json");
     FirebaseApp.initializeApp();
-  }  
-  
+  }
+
   @Test
   public void testEmptyFirebaseConfigString() {
     setFirebaseConfigEnvironmentVariable("");
@@ -481,7 +481,7 @@ public class FirebaseAppTest {
     assertNull(firebaseApp.getOptions().getProjectId());
     assertNull(firebaseApp.getOptions().getStorageBucket());
     assertNull(firebaseApp.getOptions().getDatabaseUrl());
-    assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty()); 
+    assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty());
   }
 
   @Test
@@ -542,20 +542,20 @@ public class FirebaseAppTest {
 
   @Test
   public void testValidFirebaseConfigString() {
-    setFirebaseConfigEnvironmentVariable("{" 
-        + "\"databaseAuthVariableOverride\": {" 
-        +   "\"uid\":" 
-        +   "\"testuser\"" 
-        + "}," 
-        + "\"databaseUrl\": \"https://hipster-chat.firebaseio.mock\"," 
-        + "\"projectId\": \"hipster-chat-mock\"," 
-        + "\"storageBucket\": \"hipster-chat.appspot.mock\"" 
+    setFirebaseConfigEnvironmentVariable("{"
+        + "\"databaseAuthVariableOverride\": {"
+        +   "\"uid\":"
+        +   "\"testuser\""
+        + "},"
+        + "\"databaseUrl\": \"https://hipster-chat.firebaseio.mock\","
+        + "\"projectId\": \"hipster-chat-mock\","
+        + "\"storageBucket\": \"hipster-chat.appspot.mock\""
         + "}");
     FirebaseApp firebaseApp = FirebaseApp.initializeApp();
     assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
     assertEquals("hipster-chat.appspot.mock", firebaseApp.getOptions().getStorageBucket());
     assertEquals("https://hipster-chat.firebaseio.mock", firebaseApp.getOptions().getDatabaseUrl());
-    assertEquals("testuser", 
+    assertEquals("testuser",
         firebaseApp.getOptions().getDatabaseAuthVariableOverride().get("uid"));
   }
 
diff --git a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java
index 833e2ed..189cb22 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java
@@ -328,6 +328,17 @@ public class FirebaseTokenVerifierImplTest {
 
   @Test
   public void testVerifyTokenWithTenantId() throws FirebaseAuthException {
+    FirebaseTokenVerifierImpl verifier = fullyPopulatedBuilder().build();
+
+    FirebaseToken firebaseToken = verifier.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 {
     FirebaseTokenVerifierImpl verifier = fullyPopulatedBuilder()
         .setTenantId("TENANT_1")
         .build();
@@ -341,11 +352,13 @@ public class FirebaseTokenVerifierImplTest {
 
   @Test
   public void testVerifyTokenDifferentTenantIds() {
-    try {
-      fullyPopulatedBuilder()
+    FirebaseTokenVerifierImpl verifier = fullyPopulatedBuilder()
         .setTenantId("TENANT_1")
-        .build()
-        .verifyToken(createTokenWithTenantId("TENANT_2"));
+        .build();
+    String token = createTokenWithTenantId("TENANT_2");
+
+    try {
+      verifier.verifyToken(token);
     } catch (FirebaseAuthException e) {
       assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode());
       assertEquals(
@@ -354,6 +367,23 @@ public class FirebaseTokenVerifierImplTest {
     }
   }
 
+  @Test
+  public void testVerifyTokenNoTenantId() {
+    FirebaseTokenVerifierImpl verifier = fullyPopulatedBuilder()
+        .setTenantId("TENANT_1")
+        .build();
+    String token = tokenFactory.createToken();
+
+    try {
+      verifier.verifyToken(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());
+    }
+  }
+
   @Test
   public void testVerifyTokenMissingTenantId() {
     try {
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 fd8e4af..1ebac21 100644
--- a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java
+++ b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java
@@ -261,6 +261,11 @@ public class TenantAwareFirebaseAuthIT {
       assertEquals(AuthErrorCode.TENANT_ID_MISMATCH,
           ((FirebaseAuthException) e.getCause()).getAuthErrorCode());
     }
+
+    // Verifies with FirebaseAuth
+    FirebaseToken decoded = FirebaseAuth.getInstance().verifyIdToken(idToken);
+    assertEquals("user", decoded.getUid());
+    assertEquals(tenantId, decoded.getTenantId());
   }
 
   @Test