2323import com .google .api .client .json .JsonFactory ;
2424import com .google .api .client .util .Clock ;
2525import com .google .api .core .ApiFuture ;
26- import com .google .auth .oauth2 .GoogleCredentials ;
27- import com .google .auth .oauth2 .ServiceAccountCredentials ;
2826import com .google .common .annotations .VisibleForTesting ;
2927import com .google .common .base .Strings ;
3028import com .google .firebase .FirebaseApp ;
4240import com .google .firebase .internal .NonNull ;
4341import com .google .firebase .internal .Nullable ;
4442import java .io .IOException ;
45- import java .security .GeneralSecurityException ;
4643import java .util .List ;
4744import java .util .Map ;
4845import java .util .concurrent .atomic .AtomicBoolean ;
46+ import java .util .concurrent .atomic .AtomicReference ;
4947
5048/**
5149 * This class is the entry point for all server-side Firebase Authentication actions.
@@ -65,10 +63,10 @@ public class FirebaseAuth {
6563
6664 private final FirebaseApp firebaseApp ;
6765 private final KeyManagers keyManagers ;
68- private final GoogleCredentials credentials ;
6966 private final String projectId ;
7067 private final JsonFactory jsonFactory ;
7168 private final FirebaseUserManager userManager ;
69+ private final AtomicReference <FirebaseTokenFactory > tokenFactory ;
7270 private final AtomicBoolean destroyed ;
7371 private final Object lock ;
7472
@@ -85,10 +83,10 @@ private FirebaseAuth(FirebaseApp firebaseApp) {
8583 this .firebaseApp = checkNotNull (firebaseApp );
8684 this .keyManagers = checkNotNull (keyManagers );
8785 this .clock = checkNotNull (clock );
88- this .credentials = ImplFirebaseTrampolines .getCredentials (firebaseApp );
8986 this .projectId = ImplFirebaseTrampolines .getProjectId (firebaseApp );
9087 this .jsonFactory = firebaseApp .getOptions ().getJsonFactory ();
9188 this .userManager = new FirebaseUserManager (firebaseApp );
89+ this .tokenFactory = new AtomicReference <>(null );
9290 this .destroyed = new AtomicBoolean (false );
9391 this .lock = new Object ();
9492 }
@@ -287,17 +285,31 @@ public String createCustomToken(@NonNull String uid) throws FirebaseAuthExceptio
287285 * <a href="/docs/auth/admin/create-custom-tokens#sign_in_using_custom_tokens_on_clients">signInWithCustomToken</a>
288286 * authentication API.
289287 *
290- * <p>{@link FirebaseApp} must have been initialized with service account credentials to use
291- * call this method.
288+ * <p>This method attempts to generate a token using:
289+ * <ol>
290+ * <li>the private key of {@link FirebaseApp}'s service account credentials, if provided at
291+ * initialization.
292+ * <li>the <a href="https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signBlob">IAM service</a>
293+ * if a service account email was specified via
294+ * {@link com.google.firebase.FirebaseOptions.Builder#setServiceAccountId(String)}.
295+ * <li>the <a href="https://cloud.google.com/appengine/docs/standard/java/appidentity/">App Identity
296+ * service</a> if the code is deployed in the Google App Engine standard environment.
297+ * <li>the <a href="https://cloud.google.com/compute/docs/storing-retrieving-metadata">
298+ * local Metadata server</a> if the code is deployed in a different GCP-managed environment
299+ * like Google Compute Engine.
300+ * </ol>
301+ *
302+ * <p>This method throws an exception when all the above fail.
292303 *
293304 * @param uid The UID to store in the token. This identifies the user to other Firebase services
294305 * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters.
295306 * @param developerClaims Additional claims to be stored in the token (and made available to
296307 * security rules in Database, Storage, etc.). These must be able to be serialized to JSON
297308 * (e.g. contain only Maps, Arrays, Strings, Booleans, Numbers, etc.)
298309 * @return A Firebase custom token string.
299- * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not
300- * been initialized with service account credentials.
310+ * @throws IllegalArgumentException If the specified uid is null or empty.
311+ * @throws IllegalStateException If the SDK fails to discover a viable approach for signing
312+ * tokens.
301313 * @throws FirebaseAuthException If an error occurs while generating the custom token.
302314 */
303315 public String createCustomToken (@ NonNull String uid ,
@@ -342,28 +354,43 @@ private CallableOperation<String, FirebaseAuthException> createCustomTokenOp(
342354 final String uid , final Map <String , Object > developerClaims ) {
343355 checkNotDestroyed ();
344356 checkArgument (!Strings .isNullOrEmpty (uid ), "uid must not be null or empty" );
345- checkArgument (credentials instanceof ServiceAccountCredentials ,
346- "Must initialize FirebaseApp with a service account credential to call "
347- + "createCustomToken()" );
357+ final FirebaseTokenFactory tokenFactory = ensureTokenFactory ();
348358 return new CallableOperation <String , FirebaseAuthException >() {
349359 @ Override
350360 public String execute () throws FirebaseAuthException {
351- final ServiceAccountCredentials serviceAccount = (ServiceAccountCredentials ) credentials ;
352- FirebaseTokenFactory tokenFactory = FirebaseTokenFactory .getInstance ();
353361 try {
354- return tokenFactory .createSignedCustomAuthTokenForUser (
355- uid ,
356- developerClaims ,
357- serviceAccount .getClientEmail (),
358- serviceAccount .getPrivateKey ());
359- } catch (GeneralSecurityException | IOException e ) {
362+ return tokenFactory .createSignedCustomAuthTokenForUser (uid , developerClaims );
363+ } catch (IOException e ) {
360364 throw new FirebaseAuthException (ERROR_CUSTOM_TOKEN ,
361365 "Failed to generate a custom token" , e );
362366 }
363367 }
364368 };
365369 }
366370
371+ private FirebaseTokenFactory ensureTokenFactory () {
372+ FirebaseTokenFactory result = this .tokenFactory .get ();
373+ if (result == null ) {
374+ synchronized (lock ) {
375+ result = this .tokenFactory .get ();
376+ if (result == null ) {
377+ try {
378+ result = FirebaseTokenFactory .fromApp (firebaseApp , clock );
379+ this .tokenFactory .set (result );
380+ } catch (IOException e ) {
381+ throw new IllegalStateException (
382+ "Failed to initialize FirebaseTokenFactory. Make sure to initialize the SDK "
383+ + "with service account credentials or specify a service account "
384+ + "ID with iam.serviceAccounts.signBlob permission. Please refer to "
385+ + "https://firebase.google.com/docs/auth/admin/create-custom-tokens for more "
386+ + "details on creating custom tokens." , e );
387+ }
388+ }
389+ }
390+ }
391+ return result ;
392+ }
393+
367394 /**
368395 * Parses and verifies a Firebase ID Token.
369396 *
0 commit comments