Firebase: Create custom tokens without service account credentials

Server-side Firebase applications require Google credentials to authorize the back-end service calls they make. Usually developers meet this requirement by writing a bit of code to load a Google service account JSON file, and injecting it into the Firebase Admin SDK. Listing 1 shows an example of what this looks like in Node.js.

Listing 1: Initialize Admin SDK with service account credentials

While this is not awfully complicated, it forces developers to package a service account JSON file, and also think about ways to secure that file against exposure. Service account credentials grant privileged access to a wide range of Firebase and Google Cloud services, and you absolutely don’t want it to end up in the wrong hands. Fortunately, Firebase Admin SDKs also support Google Application Default Credentials. They are a great way to decouple application code from the authorization mechanism, while not having to directly reference a service account JSON file. Application Default Credentials (ADC) are supported in Google-managed runtimes like Google Cloud Functions and Google App Engine. Listing 2 shows how to rewrite the previous example using ADC instead of service account credentials.

Listing 2: Initialize Admin SDK with Application Default Credentials (ADC)

Simpler, isn’t it? While this method of initialization enables you to use most features of the Admin SDK, as experienced Firebase developers would know, there also have been some limitations. Most notably, the Firebase Admin SDKs could not create custom tokens when initialized with ADC. Custom tokens require a private key to sign JWTs with, and the Admin SDK could only obtain one from service account credentials. Well, those days are over now! Firebase Admin SDKs just rolled out an update that allows developers to create custom tokens even when using ADC.

How does it work?

This new capability is made possible by the Identity and Access Management (IAM) API that is part of the Google Cloud Platform (GCP). IAM provides a secure endpoint that remotely signs data when invoked with a valid service account ID. Every service account has a unique ID in the GCP ecosystem, which looks something like client-id@project-id.iam.gserviceaccount.com. A service account ID can be passed to the IAM API, where it signs data using the corresponding private key (GCP knows and manages the key pair of each service account). This way, developers only have to specify the service account ID, which is not sensitive information, and avoid referencing an entire service account JSON file. Moreover, in Google-managed runtimes there are ways to auto-discover a service account ID, thus precluding the need to specify even that.

With IAM integration, the Firebase Admin SDK now follows the following protocol (in the specified order) to sign custom tokens.

  1. If initialized with a service account JSON file, the SDK simply uses the private key available within to sign tokens locally. This is essentially what the SDKs have been doing until now, so your existing applications that create custom tokens will continue to work.
  2. All Firebase Admin SDKs now accept a serviceAccountId value as an app option. If specified, the SDK invokes the IAM API to remotely sign tokens. You can find a service account ID from the Firebase or GCP console. You can also get one from a downloaded service account JSON file by looking up the client_email field.
  3. Java, Python and Go applications on Google App Engine standard environment use the App Identity Service provided by the runtime to sign tokens. This service uses a service account automatically provisioned for your app by GCP.
  4. When running in a different Google-managed runtime (Cloud Functions, Compute Engine etc), the SDK auto-discovers a service account ID by the way of the local metadata server. This service account ID is used in conjunction with the IAM API to sign tokens remotely — similar to step 2 above.

What this means is that developers on environments like Google Cloud Functions, App Engine and Compute Engine can use code similar to listing 3 to create custom tokens for Firebase clients.

Listing 3: Creating custom tokens when initialized with ADC

Depending on the exact runtime, listing 3 triggers steps 3 or 4 in the above protocol. If you don’t want the Admin SDK to auto-discover a service account ID, you can provide one explicitly so that step 2 can come into play.

Caveats and Troubleshooting

You are likely to hit a couple of snags while using the new IAM-based token signing feature. Luckily, they are easily discovered during testing, and can be fixed once and for all via a one-time configuration.

Enabling the IAM API

The IAM API is not enabled for Firebase projects by default. Therefore when the Admin SDK makes its first-ever IAM API call, it will receive an error that looks like the following:

Identity and Access Management (IAM) API has not been used in project 1234567890 before or it is disabled. Enable it by visiting
https://console.developers.google.com/apis/api/iam.googleapis.com/overview?project=1234567890 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.

Simply follow the link given in the error message, which will take you to GCP console’s IAM API configuration page. Click the big “Enable API” button, and on to the next stage.

Setting up the required permissions

In order to sign data with IAM, the service account ID passed to the API needs to have certain permissions. By default the service account ID you get from the Firebase console, and the ones auto-discovered via Cloud Functions do not have these permissions. Therefore you might get an error that looks like this:

Permission iam.serviceAccounts.signBlob is required to perform this operation on service account projects/-/serviceAccounts/{your-service-account-id}.

To resolve this error, you need to go to the IAM and admin page of the GCP console, and assign the Service account token creator role to the service account in question.

Refer to the official documentation for more details on these caveats, and detailed step-by-step troubleshooting instructions. Also, in the future these issues are likely to be addressed at the Firebase/GCP infrastructure, thereby resulting in a more amicable on-boarding experience for developers.

I’m not using a Google-managed runtime. What should I do?

ADC is a feature supported in Google-managed runtimes. Therefore the simplest option for developers using other environments is to just keep using service account credentials. The service account JSON file plays two roles in this context — credentials and signing mechanism. Finding replacements for both without GCP runtime support is not easy.

However, you can still make things better by separating your code from the service account JSON file. Have a service account JSON file installed in your runtime environment, and set the GOOGLE_APPLICATION_CREDENTIALS environment variable to point to it. Then you can use the usual ADC initialization syntax in your code (listing 2). Sure, it doesn’t completely eliminate the service account JSON file, but at least it decouples your code from the credentials, giving you greater portability and flexibility. Depending on your exact DevOps flow, you might even be able to delegate the deployment of the service account JSON file to your sysadmins or SREs.

There might be a way to completely eliminate the service account JSON file by using the service account ID option in combination with custom or refresh token credentials. This is untested, and I’d like to experiment with it a bit more before going into details. If somebody has ever tried something like this, please let me know. Also, I’d like to hear other ideas on how this scenario can be handled better.

The GOOGLE_APPLICATION_CREDENTIALS environment variable is also good for locally testing your code, before it is pushed out to GCP for deployment. It gives you a way to run the same code in both production GCP and your local development environments.

What about signing Cloud Storage URLs?

If you have ever tried to create signed URLs for your Cloud Storage objects from Firebase Admin SDK, then you know that’s another use case that requires explicit service account credentials. In this case the signing is handled by the Google Cloud Storage (GCS) libraries that the Firebase Admin SDK depends on. These libraries haven’t changed much, so you will have to continue to use service account credentials for this particular use case. But I do have some good news for Node.js developers!

The GCS library for Node.js does support IAM-based signing now, when deployed in Google-managed runtimes. It basically implements steps 1 and 4 of the new signing protocol used by the Firebase Admin SDK. This means Node.js developers on Google Cloud Functions can go full service account-less now. Developers on other languages will have to bear with the inconvenience for now, until the necessary changes become available in the corresponding GCS libraries.

Conclusion

I hope this post gives a bird’s eye view of this new and important update introduced to the Firebase Admin SDKs. Official documentation also covers a lot of this and more, so be sure to check that out too. Ability to sign tokens without explicit service account credentials is a big step towards making the server-side Firebase story better, more versatile and friction-less. But it also has some room to grow, and I’d like to hear your ideas and suggestions on how it can get there.

Hiranya Jayathilaka

Written by

Software engineer at Google. Enjoys working at the intersection of cloud, mobile and programming languages. Fan of all things tech and open source.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade