The arrival of v2 of the Prelude API and the associated SDKs, integration of Firebase Auth with Prelude is easier than ever before.

Here we show a sample Go application that allows you to verify a user’s phone number to authenticate them (or create and account if necessary) in Firebase Auth.

See the full example at https://github.com/prelude-so/thirdparty-integrations/tree/main/firebase/go

In this sample we will create a very simple server application that exposes 2 endpoints, /send_code and /verify .

Clients of this service will use the /send_code endpoint to request the verification of a phone number. The phone number referenced here will receive a SMS message with the verification code. Then a second call to /verify including the phone number and the received verification code will validate that the code is correct and, if so, a user is created or authenticated (if they already exist) in Firebase Auth.

Prerequisites

This sample uses the current Prelude Go SDK (v0.1.0) compatible with the Prelude V2 API, and the current Firebase Go SDK (v4). To run it correctly you will need:

  • A recent Go version. To set this up, follow the Go docs.
  • A Prelude account. If you don’t have one already, you can sign up here.
  • A Firebase account and a project set up. Follow the instructions here. You will need Auth to be enabled and we will use the Firebase Admin SDK, as this is a server application. From the Firebase project settings (in the “Service accounts” section) you will need to generate a new private key file if you don’t already have one.

Implementing the example application

Setting up the project

Create a new directory somewhere in your system and configure the Go module and dependencies:

  • Create a new Go module: go mod init example.com/prelude-firebase-integration
  • Install the Prelude Go SDK: go get github.com/prelude-so/go-sdk
  • Install the Firebase SDK: go get firebase.google.com/go/v4@latest
    • Have the Firebase Service account file available as we will need it in the code.
  • In your Prelude account dashboard, select the application you want to use and generate an API key if you don’t have it already.
    • Select your application, go to Settings, Keys and click “Create”. Make a note of this key as you can not reveal it again in the dashboard and will need to create a new one if you lose it.

Some global configuration

The sample aims to be as simple as possible so we are going to have a few top level constants to hold our configuration parameters, namely the post we want out HTTP server to listen to, the path to our Firebase service account file, and the Prelude SDK key we got from the Prelude application settings.

We can create a server.go file in the same directory where we created the Go module, use it as our main entry point, and add the required imports and constants at the top of the file:

package main

import (
	"context"
	"encoding/json"
	"log"
	"net/http"

	firebase "firebase.google.com/go/v4"
	"firebase.google.com/go/v4/auth"
	"github.com/prelude-so/go-sdk"
	preludeOption "github.com/prelude-so/go-sdk/option"
	googleOption "google.golang.org/api/option"
)

const port = ":8888" // Choose your desired port here
const firebaseCredentialsFile = "[PATH TO THE SERVICE ACCOUNT FILE]" // Use env variables if desired
const preludeApiKey = "[YOUR PRELUDE API KEY]" // sk_XXXXXXXX

As we have two option dependencies (from Google and Prelude) we create aliases for them.

The main function

We want to the server.go file to contain everything in this example so we are adding a main function in it that will configure the HTTP server and the 2 required endpoints.

func main() {
	ctx := context.Background()
	opt := googleOption.WithCredentialsFile(firebaseCredentialsFile)

	app, err := firebase.NewApp(ctx, nil, opt)
	if err != nil {
		log.Fatalf("error initializing app: %v\n", err)
	}

	authClient, err := app.Auth(ctx)
	if err != nil {
		log.Fatalf("error initializing auth client: %s", err)
	}

	client := prelude.NewClient(
		preludeOption.WithAPIToken(preludeApiKey),
	)

	http.HandleFunc("/send_code", SendCode(client, ctx))
	http.HandleFunc("/verify", Check(client, ctx, authClient))

	log.Printf("Listening on port %s", port)

	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatalf("Start server error: %v", err)
	}
}

In this function we have:

  • Configured the required Firebase clients (the Firebase app and the Firebase Auth client) using the Firebase service account file. This used the installed Firebase Go SDK.
  • Configured the Prelude client with the Prelude API Key. This used the Prelude Go SDK.
  • Defined the 2 required endpoints (/send_code and /verify) which will subsequently call the functions described below.
  • Start a simple HTTP server on the configured port.

With these steps, we have a basic server ready to operate when the application is run.

Requesting a verification code

In this example, to initiate the verification of a phone number, we need to interact with the /send_code endpoint. To achieve this, a client application should issue a POST HTTP request with the following structure:

POST http://localhost:8888/send_code
Content-Type: application/json

{
  "phone_number": "+[COUNTRY_CODE][PHONE_NUMBER]"
}

where phone_number represents a E.164 formatted mobile phone number. For instance, a French mobile number would be formatted as: +33XXXXXXXXX.

Upon receiving such a request, the server will invoke the SendCode function, which will be outlined next:

func SendCode(preludeClient *prelude.Client, ctx context.Context) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		type request struct {
			PhoneNumber string `json:"phone_number"`
		}

		var req request
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, "invalid request", http.StatusBadRequest)
			return
		}

		_, err := preludeClient.Verification.New(ctx, prelude.VerificationNewParams{
			Target: prelude.F(prelude.VerificationNewParamsTarget{
				Type:  prelude.F(prelude.VerificationNewParamsTargetTypePhoneNumber),
				Value: prelude.F(req.PhoneNumber),
			}),
		})
		if err != nil {
			http.Error(w, "invalid request", http.StatusBadRequest)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

This function is fairly straightforward. We parse the JSON body from the request and extract the phone number in it. Then, we simply call the preludeClient.Verification.New function with it. If the Prelude client is correctly configured with the API key, a verification code will be sent to the requested phone number. In the success scenario (happy path) this function just returns a HTTP 204 status code.

Checking the verification code

Once the mobile phone receives the verification code, the client application must call the /verify endpoint of the server to verify that the code is correct. The expected request for this endpoint has the following shape:

POST http://localhost:8888/verify
Content-Type: application/json

{
"phone_number": "+[COUNTRY_CODE][PHONE_NUMBER]",
"code": "[VERIFICATION_CODE]"
}

As you can see it includes a simple JSON body with the same phone_number field plus a new code field that should be filled with the verification code received in the phone (from 4 to 8 digits code).

Once the endpoint receives the request, it will call the Check function:

func Check(preludeClient *prelude.Client, ctx context.Context, firebaseAuthClient *auth.Client) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		type request struct {
			PhoneNumber string `json:"phone_number"`
			Code        string `json:"code"`
		}

		var req request
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, "invalid request", http.StatusBadRequest)
			return
		}

		checkResult, err := preludeClient.Verification.Check(ctx, prelude.VerificationCheckParams{
			Target: prelude.F(prelude.VerificationCheckParamsTarget{
				Type:  prelude.F(prelude.VerificationCheckParamsTargetTypePhoneNumber),
				Value: prelude.F(req.PhoneNumber),
			}),
			Code: prelude.F(req.Code),
		})
		switch {
		case err != nil:
			http.Error(w, err.Error(), http.StatusBadRequest)
		case checkResult.Status != prelude.VerificationCheckResponseStatusSuccess:
			http.Error(w, checkResult.JSON.RawJSON(), http.StatusBadRequest)
		default:
			firebaseToken, err := CreateFirebaseUser(r.Context(), firebaseAuthClient, req.PhoneNumber)
			if err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			} else {
				err := json.NewEncoder(w).Encode(struct {
					Token string `json:"token"`
				}{firebaseToken})
				if err != nil {
					return
				}
			}
		}
	}
}

This function is a slightly longer but very similar to the previous one.

First we extract the phone number and verification code from the HTTP request body and then we proceed to call the preludeClient.Verification.Check function from the Prelude Go SDK with those parameters. Here we can simply examine the returned result and if it is a successful result (prelude.VerificationCheckResponseStatusSuccess) we can proceed to create or authenticate the user in Firebase. For that we will use the CreateFirebaseUser function.

Creating the Firebase User

Up to this point we have not used the Firebase SDK at all (besides the initial setup). Once we have received confirmation that the code is correct, we can proceed with the Firebase Auth SDK. Here’s a straightforward implementation:

func CreateFirebaseUser(ctx context.Context, firebaseAuth *auth.Client, phoneNumber string) (res string, err error) {
	user, err := firebaseAuth.GetUserByPhoneNumber(ctx, phoneNumber)
	if err != nil {
		newUser := (&auth.UserToCreate{}).PhoneNumber(phoneNumber)
		createdUser, err := firebaseAuth.CreateUser(ctx, newUser)
		if err != nil {
			return "", err
		}
		user = createdUser
	}

	customToken, err := firebaseAuth.CustomToken(ctx, user.UID)
	if err != nil {
		return "", err
	}
	return customToken, nil
}

To create or authenticate the user in Firebase, we just need the phone number (which is already verified).

This function will first try to get the user corresponding to the given phone number and if it can’t find it, it will create a new one.

Once we have the user from Firebase we obtain a new custom token for them and return it. The client application then should be able to use the token in place of the user.

Conclusion

As you can see, it is rather simple to add Prelude verification to a Firebase application. If you have any questions about the implementation of Prelude, we’ll be happy to help at hello@prelude.so.