Getting Started with Firebase for Server-Side Swift

Connecting Swift Vapor to Firebase’s Cloud Firestore Database

Tyler Milner
Atlas
18 min readMar 7, 2018

--

The Firebase suite of tools is awesome. It’s great to have so many useful development services bundled under one roof, and the ability to get started for free is great! Firebase offers SDKs for several platforms to communicate with their services, including iOS. However, the iOS SDK is heavily coupled to the iOS application lifecycle and thus not usable in a server-side Swift environment. Luckily, Firebase offers a REST API which gives us an avenue to use their services when developing a server in Swift. Having done just that recently, this post outlines the steps needed to get started with the Firebase REST API in a server-side Swift environment. I’ll be connecting a Swift Vapor app to Firebase’s new Cloud Firestore technology, but the general process should apply to any other server-side Swift framework and Firebase service.

The Tech Stack

Cloud Firestore

Firebase Cloud Firestore

Cloud Firestore is Firebase’s “new and improved” NoSQL cloud database. As with their previous product, Firebase Realtime Database, Cloud Firestore syncs data in realtime across clients and offers first-class support for offline mode.

Cloud Firestore vs Realtime Database

If you’ve used Firebase’s Realtime Database product in the past, you may be wondering how Cloud Firestore is different. The Firebase documentation goes into more detail, but it basically boils down to Firebase improving upon the successes of the original Realtime Database product. The older Realtime Database required denormalization and data flattening techniques to prevent bottlenecks as your database grew and evolved. Cloud Firestore improves upon this by offering richer, faster queries and better scalability.

So if Cloud Firestore is an improved version of Realtime Database, is Realtime Database even relevant anymore? The answer is yes. The real strength of the Realtime Database continues to be its speed. If you need to optimize your product for efficient, low-latency syncing, then you will probably want to use Realtime Database instead of Cloud Firestore. That’s not to say that Cloud Firestore won’t do the job. It still syncs things super fast (within milliseconds).

Vapor 2.x

Server-side Swift Vapor

Vapor is one of the many server-side Swift libraries on the market. I feel they’ve done a great job keeping things simple to understand and making it easy to get started with server-side Swift development. They even recently launched Vapor Cloud, which makes it ridiculously easy to deploy your server into the real world. It’s even free for small projects! For this project, I used Vapor 2.x since Vapor 3 was still in beta when I started.

The Sample Project

I’ll be using these technologies to implement a simple proof-of-concept for utilizing server-side Swift to implement custom backend logic while relying on Firebase for realtime updates to client apps. For this demo, the server will simply be generating a random number every 60 seconds and then publishing that number to the Cloud Firestore database along with the date that it will generate the next random number. When everything is up and running, the client app will look something like this:

The iOS client app for this sample project

Setting up Firebase

The first thing you’ll want to do after creating your Firebase project is to enable Cloud Firestore. This is easy enough to do. Open your Firebase project and navigate to Develop → Database and turn it on:

Enabling Firebase Cloud Firestore

Once enabled, you’ll see an empty database that looks something like this:

Empty Cloud Firestore database

Connecting to Firebase

Now that we’ve got Cloud Firestore enabled for our project, how can we connect to it? As I mentioned above, the Firebase iOS SDK is off the table. Perhaps the SDK will someday be compatible with server-side Swift, but the Firebase team currently has no concrete plans in place.

Since we can’t use the iOS SDK, our options for connecting to Cloud Firestore from Vapor are either using the REST API or the RPC API. Since I don’t know enough about RPC yet, we’ll be using the REST API for this example.

If you’re using the older Realtime Database product, I should mention that you actually have another option. There exists an open source Swift library called FirebaseSwift that provides some conveniences for connecting to Realtime Database from a server-side Swift environment. However, you’ll still need to manually generate and manage your Google OAuth token, which I’ll talk about in a bit.

Cloud Firestore REST API

Using the Cloud Firestore REST API is as simple as:

  • Authenticating with Google to receive a server-to-server access token.
  • Using that access token to read/write data to the Cloud Firestore database.

Google Server-to-Server OAuth

Authentication Options

There are three ways that you can authenticate with Google to receive a server-to-server access token:

Although Google strongly encourages you to use one of their client libraries, Swift is not supported yet. 😔

Google’s recommendation to use one of their client libraries to authenticate

This means that we’ll need to manually implement the OAuth process. I haven’t looked into the gcloud command-line tool, but it seems to me that it’d only really be useful for generating one-off tokens for testing.

Authentication Prerequisites

In order to authenticate with Google, you’ll need to use a Google “service account”. A service account is an account that belongs to your application itself rather than an individual user of your app. A service account is composed of two things:

  • A unique email address, which is auto-generated for you by Google.
  • A public/private key pair.

Obtaining a Google Access Token

So we’ve established that we’ll need to manually follow Google’s OAuth process. How does that work? The process can be summarized in three steps:

  • Create a JSON Web Token (JWT), signed with your service account’s private key.
  • Use the JWT to request an access token from the Google OAuth 2.0 Authorization Server.
  • Handle the JSON response from the Authorization Server.

A sequence diagram of this flow looks something like this:

Google server-to-server OAuth process

I should note that Google’s documentation again calls out that they’d really like for you to use one of their client libraries to authenticate. I think they’re trying to tell us something…🤔

Google again recommending to use their client libraries for authentication

Since there’s no Swift-compatible Google authentication library available, we’ll just have to ignore Google’s persistent attempts to dissuade us from manually implementing their OAuth flow. But first, a quick crash course on JWTs…

JWT — A Quick Refresher

A JWT, pronounced “jot”, is basically just a piece of data composed of three parts:

  • A “header”, which specifies the format of the token and the signing algorithm used.
  • A “payload” (or “claims”) which holds information about the token, including things like which user the token represents, what permissions (scopes) they have available, and the token lifetime.
  • A “signature”, which is a cryptographic signature of the header and claims appended together.

Each part of the JWT is base64 url-encoded and then appended together with periods to form the final representation of the JWT. Overall, the process looks something like this:

How a JWT is formed

It’s important to note that even though it may look like it, a JWT is not encrypted. This means that anyone can read the contents of the “claims”. If you are ever creating JWTs for your own purposes, don’t store any sensitive information in them. The cryptographic signature of the JWT simply allows the entity that issued the JWT to verify the integrity of the data. If a party tries to modify any of the information in the “header” or “claims”, then the signature will indicate that the data is invalid and the issuer can reject the token.

Generating the Google OAuth JWT

So how do we generate the Google OAuth JWT using Vapor? In order to generate the JWT, we need to satisfy two prerequisites:

  • Obtain the Google Service Account credentials that I mentioned earlier (specifically the private key).
  • Configure the Vapor project by adding the JWTProvider helper library and configuring a Signer with the service account’s private key.

Google Service Account Credentials

You can manage serve accounts on the Google Service Accounts page, but we can also access the service account associated with our Firebase project through the Firebase Console, which is the process that we’ll follow right now.

It’s important to note that even though Google generates the public/private key pair for your service account, they don’t keep a copy of the private key. It’s up to you to keep track of it and store it securely.

Downloading the Credentials

In order to download your service account credentials, first open your project’s settings on Firebase by tapping the gear icon next to “Project Overview” and then selecting “Project Settings”:

Accessing Firebase project settings

Next, navigate over to the “Service Accounts” tab and then click on the “Generate New Private Key” button at the bottom:

Downloading the service account private key

This will download a JSON file to your computer, which is Google’s preferred format for distributing these private keys.

Credentials JSON Format

Open up your service account credentials JSON file. It should look something like this:

Google Service Account credentials JSON file

Notice the private_key field. This is what the Signer in our Vapor app will need to generate and sign the JWTs used for Google’s server-to-server OAuth process.

Transforming the Private Key

Unfortunately, the private key in its current form will not work for us. The main issue with this version of the key is that it’s just a regular “private key”. What we need is an “RSA private key”. This small detail caused me hours of pain until a friend enlightened me to the fact that there are different types of private keys.

Create a File for the Key

The first thing we need to do is copy the value of the private_key field in the credentials JSON to its own file. Save this file as GooglePrivateKey.key.

Clean up Whitespace

If you look at the private key right now, you might notice that there are several \n newline characters:

We need to get rid of those by converting them into actual newlines. Go ahead and highlight each \n occurrence and press “Enter” on your keyboard to replace it with a newline. You should end up with something like this:

If you know of a better way to do this instead of manually, please let me know! I think there’s a way to do this with the sed command on macOS, but I haven’t had time to experiment with it yet.

Convert the Private Key into an RSA Private Key

Next, use OpenSSL to generate an RSA private key from your newly cleaned up private key:

Prepare RSA Private Key for Use in Vapor

We’re almost done transforming this private key into the format we want. The next thing we need to do is convert this RSA private key into a single line. Open up GooglePrivateKey_RSA.key in a text editor and remove all of the newlines so that the entire key is on one line.

Next, remove the -----BEGIN RSA PRIVATE KEY-----and -----END RSA PRIVATE KEY----- so that we just have the contents of the key itself in a single line. You should end up with something like this:

Setting Up Vapor

Now it’s almost time to start writing our server-side Swift code. Aren’t you excited?! There’s just a few setup items that we need to take care of first. I’m assuming that you’ve already got Vapor installed and a project created. If not, follow Vapor’s getting started instructions.

Adding JWTProvider

Add the JWTProvider dependency to your Package.swift file:

Adding JWTProvider to your Vapor project’s Package.swift file

Next, regenerate your Xcode project:

or:

If it’s not already open, go ahead and open the Xcode project as well.

Initialize JWTProvider

Open the Config+Setup.swift file and initialize the JWTProvider in the setupProviders() method:

JWT Config

The final step involved with setting up JWTProvider is to create a jwt.json configuration file. Go ahead and make a file called jwt.json in the Config directory:

Creating the Config/jwt.json file

Populate Config/jwt.json with the following contents:

Here’s what’s going on in the JSON file:

  • A top-level object called signers must be present.
  • For this example, we only have one signer called googleOAuth.
  • The googleOAuth signer specifies the algorithm and key used to perform the signing.

Notice that we did not paste in that single-line RSA private key that we just created. More on this in just a second, but we’re actually using the syntax for injecting environment variables into the JSON file. There are other options for how you configure your jwt.json file as well. If you’d like to know more about that or JWTProvider, feel free to check out the Vapor JWT documentation.

Vapor Development Process

Before we go any further, I’d like to take a moment to discuss my Vapor development process. I’ve found that it’s easiest to get started by putting all of your logic into the Routes.swift file. This way, you can test/trigger your logic by issuing a request to your route using something like Postman. If you’re route is a GET, then I suppose you could trigger it via your browser as well.

For this example, I started by creating a POST route called “nextRandom”:

Securing the Private Key

As I mentioned previously, we need to be extra careful with our private key. In the words of the wise wizard Gandolf:

Keep it secret…keep it safe…your private key, that is

Just Say No to Source Control

I’ll say this just once, but I’ll say it loud:

NEVER COMMIT YOUR PRIVATE KEY INTO SOURCE CONTROL!

Consider yourself warned.

Setup the Private Key Environment Variable

As I mentioned above, the value of the key in our Config/jwt.json file is set to an environment variable. This allows us to keep the key out of source control by injecting it at runtime.

To set this up, edit your app’sRun scheme and add an environment variable called GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY. Next, paste your single-line RSA private key as the value for GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY:

Setting up the Run scheme with the GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY environment variable

Please note that if you’re using a “shared” scheme, do not do this! Shared schemes are often committed into source control so you wouldn’t be doing yourself any favors by setting up this environment variable.

Instead, if you’re using a shared scheme, create your jwt.json file inside of the Config/secrets directory. Just make sure to double-check that the Config/secrets directory is included in your .gitignore file so that it’s excluded from source control. The default Vapor .gitignore already has this directory excluded:

Private Key Gotchas

Just a few “gotchas” to be aware of involving your private key:

  • Make sure to securely store a copy of your cleaned, single-line RSA private key. This is mainly just a convenience so that you don’t have to reconvert the key from its original JSON file format if you ever need to reconfigure your Xcode project.
  • Deleting the Xcode project will wipe out the GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY environment variable. However, simply regenerating the Xcode project is fine.
  • Your app will crash on launch if the private key in your Config/jwt.json file is missing or invalid.

That last point is important. JWTProvider will fail to initialize if it can’t generate a valid RSA private key based on the values in your jwt.json configuration file. If this happens, your Vapor app will crash with a JWT error: Could not create key error:

JWTProvider’s crash on launch when it is configured with an invalid RSA private key

Authenticating with Google using Vapor

Finally, we’ve got all the setup out of the way and can begin writing our server-side Swift code! 🎉 Go ahead and open Routes.swift and get ready to start filling in the implementation of the POST /nextRandom endpoint. If you want to peek ahead to what this route will look like when we’re done, then feel free to take a look at the Routes.swift file on the feature/basicExample branch.

Creating the Google OAuth JWT

Use Vapor’s JSON type to create the JWT headers:

Up next, we’ll create the claims:

Notice that we’ve given our JWT a lifetime of 30 minutes. Feel free to change this value to suit your needs. A smaller value is arguably more secure, and the maximum value that Google will accept is 60 minutes.

To actually create the JWT, we need to get a reference to the Signer object that represents the “googleOAuth” signer in our jwt.json configuration file. From there, we can create the final, encoded JWT from the headers, claims, and Signer:

Sending the OAuth Request

We’ll use Droplet’s client property to perform a POST request onto Google’s OAuth URL using the following URL-encoded form parameters:

  • grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
  • assertion=<value of the JWT>

A successful response from Google will contain an access_token, which is what we’ll need to provide in our requests to Firebase:

Storing Data in Firebase Cloud Firestore

Now that we’ve got authentication with Google out of the way, we’ll be sending data to Firebase in no time! But first, a little about the data structures used in Cloud Firestore.

Cloud Firestore Data Structures

In Firestore, everything is a Document, which are organized into Collections. A Document is basically just JSON that follows a certain structure. Collections and Documents are also created implicitly. Just write the data you want where you want and Cloud Firestore will take care of the rest! Here’s a peek at how our Firestore database will look once the root collection and its root Document are added:

Organization of Cloud Firestore database Documents into Collections

Cloud Firestore Document

So what does a Firestore Document look like? At a minimum, it must contain a name and a fields property. The fields property describes your data using JSON. Firestore also manages createTime and updateTime properties for you:

And here’s what you have at your disposal when creating your fields object:

Feel free to reference Firestore’s documentation if you’d like to learn more about the Firestore Document model.

Authenticating with Firestore

In order to authenticate your requests to Cloud Firestore, simply set the value of the Authorization header to the access token obtained during the Google OAuth process:

As a side note, remember that the scope specified in the “claims” of the JWT that we created had a value of https://www.googleapis.com/auth/datastore. This scope is what tells ultimately tells Firebase that it’s okay for you to access the Firestore database.

Accessing Firestore Data

The Firestore REST API follows common CRUD operational patterns. CRUD is an acronym for Create Read Update Delete. The Firestore documentation contains details on the full list of operations available:

Cloud Firestore REST API operations

In order to form a Cloud Firestore request, you simply append the path to the Document in your database onto the Firestore base URL. For example, the base URL https://firestore.googleapis.com/v1beta1/ and document path /projects/YOUR_PROJECT_ID/databases/(default)/documents/cities/LA generate a final request URL that looks like this:

Creating a Firestore Document

For this example, we need to create a Document that will represent a random number. It will also include the date that the next random number will be drawn. The JSON representation for this Document will end up looking something like this:

And the code that builds this:

Sending the Document to the Firestore Database

First, as mentioned above, we need to form the URL that points to our Document in the Firestore database:

Next, we’ll use the patch method to instruct Firebase to update or insert the Document that we send:

Finally, go ahead and make a call to your POST /nextRandom route. With any luck, you should get back the Firestore response representing the Document that was just created.

Completed Sample Project

Up until this point, we’ve put all of our logic inside of the Routes.swift file. However, in a production app this is highly undesirable. Feel free to check out the completed sample project on GitHub to see how I organized the sample project to keep the Routes.swift file a little more lean. The completed sample also starts generating random numbers and sending them to Firestore right away. I left the implementation of the POST /nextRandom route in there, but technically it’s not necessary to call it anymore.

I’ve also posted the iOS client app on GitHub. The client app simply observes the “randomNumbers/theRandomNumber” Document in the Firestore database. Each time a new update comes in, it displays the random number and then begins counting down the seconds until the next random number is posted based on the nextUpdate property.

Future Improvements

There are still some improvements that could be made to this example. One thing that you will most certainly want to take care of if you decide to use this technique in a production app is to cache the Google access token instead of performing the Google server-to-server OAuth process each time you communicate with Firebase. The access tokens you receive from Google expire after one hour so you’ll need to keep track of the expiration date of your token based on the value of the expires_in property.

Another improvement might be realized by utilizing the Firebase RPC API. From what I understand, gRPC utilizes HTTP/2 and enables realtime bidirectional streaming which could really speed things up in the communication channel between your server and Firebase. In the future, I may try to incorporate RPC as the next evolution of this demo app.

Conclusion

Firebase and server-side Swift are some of my favorite new tools in my toolbox as a mobile application developer. While connecting to Firebase from a server-side Swift environment isn’t quite as streamlined as connecting to Firebase from an iOS app, it’s still simple enough to do that it remains a viable option if you‘re looking to incorporate Firebase into your Swift backend.

--

--