Custom Token Authentication with Firebase


Why ?

Firebase is giving only a set of limited methods for authenticate users like email/password, mobile number, google, facebook, etc. It has almost everything that most people need. But sometimes we need to use a custom authentication server which is in the premises, an LDAP server or some other OAuth2 provider. That’s when custom token authentication method in the Firebase Authentication Server is useful.

But why use Firebase Authentication without using the authentication system that we are currently having (as mentioned above)? Some reasons that come into my mind are listed below.

  1. Firebase Authentication can be easily used within any GCP service.
  2. Firebase Authentication client/admin SDK is in lots of languages. So it will be easier to use. These SDKs also consist with lots of built in features.
  3. We can give multiple authentication methods for the same user. If a user needs to login with his email/password and with gmail too, we can easily do that with Firebase.
  4. We can easily merge 2 users into a single user.

Method

Before going into the code, I will describe how custom token authentication is done in few steps.

  1. Client app will send a request to this API endpoint with the authentication credentials to the 3rd party auth server to get the custom token.
  2. This API will use the credentials and authenticate the client in the 3rd party authentication server. (As mentioned above).
  3. If the authentication was successful use a key which can be used to uniquely identify the client, to create a custom token using Firebase Authentication Admin SDK.
  4. Send the custom token to the client with authentication successful message.
  5. Client can login using the received token with Firebase Authentication Client SDK.

Endpoint

I will use a cloud function as the backend server. If you are new to cloud functions, you can find some articles here in this blog. Also you can read the documentations given by the google. Take a look at the cloud function documentation from Firebase. I find it really easy compared to the GCP cloud function documentation.

I’m using a cloud function with HTTPS endpoint for this demonstration. But with few modifications, this can also be used for callable cloud functions. You can see a simple cloud function below that will return "Hello World" as the response after calling the API. I used typescript for coding.

1
2
3
4
5
6
7
8
9
10
11
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors');
import {Request, Response} from 'express';

const app = express();
app.use(cors({origin: true}));

app.post('/', (req: Request, res: Response) => res.send("Hello World.\n"));

exports.api = functions.https.onRequest(app);

You can use the following cURL command to test the cloud function. Make sure to update the cloud function endpoint with the one you got after deploying.

1
curl --location --request POST 'https://us-central1-fcode-blog.cloudfunctions.net/api'

Generate Custom Token

Before generating a custom token, we need a separate authentication server. To mock a separate authentication server, I will just create an async function as follows. I will call it the mock auth system. This system will return true if the given user is authenticated and false if not.

1
2
3
4
const mockAuth = async (id: String, passcode: String) : Promise<boolean> => {
await new Promise(resolve => setTimeout(resolve, 1000));
return id === 'ABC-1234' && passcode === '123456';
}

The only user in the mock auth system has the id ‘ABC-1234’ and passcode ‘123456’. We need to modify the endpoint to

  1. accept the credentials
  2. authenticate the client in mock auth system
  3. return the custom token

Therefore, the definition of the endpoint has to be changed as follows.

Method: POST
Content-Type: application/json
Body Payload:

Property Name Type Description
id String The id the client is signing in with, in the mock auth system
passcode String The passcode of the mock auth account of the client

Response Payload:

Property Name Type Description
id String The id the user who authenticated in the mock auth
customToken String Custom token that can be used to log into Firebase Auth

A custom token can be generated with Auth.createCustomToken(id) method. It will require an ID that can be used to uniquely identify the user inside Firebase. We know the id given as the credential is unique in the mock auth system. To make it unique inside Firebase, we will simply use the suffix "MOCK". Then the unique id that will be used to create custom token will be MOCK-ABC-1234.

The following typescript code will return a custom token as I discussed earlier. It will not do any validation or edge case testing. Just an implementation of what I explained.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.post('/', async (req: Request, res: Response) => {
const id : String = req.body['id'];
const passcode : String = req.body['passcode'];
const authenticated = await mockAuth(id, passcode);
if (!authenticated) {
return res.status(400).send({'msg': 'Invalid id or passcode'});
}
try {
const token = await admin.auth().createCustomToken(`MOCK-${id}`);
return res.status(200).send({
'id': id,
'customToken': token
});
} catch (e) {
return res.status(500).send(`Unexpected error while generating custom token.\n${e}`);
}
});

As we are using Firebase Admin SDK, we have to add these lines before calling the admin SDK for the first time.

1
2
const admin = require('firebase-admin');
if (admin.apps.length === 0) admin.initializeApp();

After deploying the new function with npm run deploy, you can run the previous cURL command. You see the ‘invalid id or passcode’ msg as expected. You can use the following cURL command to create a custom token.

1
2
3
4
5
6
curl --location --request POST 'https://us-central1-fcode-blog.cloudfunctions.net/api' \
--header 'Content-Type: application/json' \
--data-raw '{
"id": "ABC-1234",
"passcode": "123456"
}'

When you run the above command, you will get the following error message instead of the custom token.


IAM Service Account Credentials API has not been used in project 217740369248 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/iamcredentials.googleapis.com/overview?project=217740369248 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.;
Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens for more details on how to use and troubleshoot this feature.



Service Account Configuration

This happens because you need to enable IAM Service Account Credential API for this project. That service is used to sign a JSON Web Token (JWT), which is a JSON text with some information that can be used to uniquely identify the user. JWTs created by Firebase are using OpenID Connect JWT specs and therefore they have these information in it. Custom token which is created by Firebase Admin SDK is really a signed JWT. Therefore the project should have the above mentioned service enabled.

There are 3 ways that can be used to enable that service. All of them are described in this documentation. I will use the 2nd method here, which is letting Admin SDK to automatically detect the service account. It is the most secure and least painful way. (Actually all these methods are very secure in the Google’s end. I said the other 2 are less secure because they involve some security measures that need to be taken place by the programmer also. You know, programmers!!).


To enable the service you need to,

  1. Enabling the IAM Service

Click the link given by the error message, which is https://console.developers.google.com/apis/api/iamcredentials.googleapis.com/overview?project=217740369248. You can also go to this page by visiting APIs & Services -> Library in GCP console. Then search for IAM Service Account Credential API.

Manually enable Service

Then click Enable API button. It will take some time to enable it.


  1. Add iam.serviceAccounts.signBlob permission

Then visit Access -> IAM page in the GCP console.

IAM Section of the Console

Then locate the service email address which is in the format <project_id>@appspot.gserviceaccount.com. Click the edit icon at the right side of the table, next to that email.


Edit service account button

Then click “+ ADD ANOTHER ROLE” button in the appeared pane. Select the role as “Service Account Token Creator”. If everything is as follows, click save.

Edit permission to have signBlob permission

You will have to wait about 5 minutes to populate everything. Then use the last cURL command to get the custom token. You will see the following.

1
2
3
4
{
"id": "ABC-1234",
"customToken": "<CustomToken>"
}

Now you can use this Custom Token to log into firebase authentication server. In the following section, I will put some code for authentication in different clients.


REST Client

To access Firebase REST API, you need an API key. You can get the API key to your project from the Firebase Console. Goto Project Settings section, General Tab. Under “Your Project” you will see a value with the key “Web API Key”. Copy that value and that is you API key.

You can run the following cURL command to login. Replace [API_KEY] with the copied “Web API Key” and [CUSTOM_TOKEN] with the token you received from the previous step using the endpoint just created.

1
2
3
4
5
6
curl --location --request POST 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=[API_KEY]' \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "[CUSTOM_TOKEN]",
"returnSecureToken": true
}'

You will get a json output and you can use those values in other endpoints in Firebase REST API.


Web Client

First you need to setup your web application to support Firebase. You can see the official documentation here. The use the following code to login in with the CUSTOM_TOKEN.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
firebase.auth().signInWithCustomToken(CUSTOM_TOKEN)
.then((userCredential) => {
// Signed in
// ...
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
if (errorCode === 'auth/invalid-custom-token') {
alert('The token you provided is not valid.');
} else {
console.error(error);
}
});

Flutter Client

First you need to setup your mobile/web application to support Firebase. You can see the official documentation here. Then use the following code to login in with the CUSTOM_TOKEN.

1
2
3
4
5
6
7
8
FirebaseAuth.instance.signInWithCustomToken(CUSTOM_TOKEN)
.then((userCredential) {
// Signed in
// ...
})
.catch((error) {
print(error);
});

Other mobile clients

For other clients also, first you need to setup your project with Firebase. Then you have to use appropriate API.


Conclusion

First you need to understand why you need custom token authentication for your app. It is because you need to authenticate your app with a 3rd party authentication server (maybe some OAuth2 server), but at the same time you need all the cool features Firebase Authentication brings to your app. I think I cover all the things that you need to know when generating a Custom Token and when you are using it in the client side of the application. And as always, write a comment if you have anything to ask. I create a gist with this code and you can access it via following link.

Custom Token Authentication

Custom Token Authentication

This Github gist contains a sample code to generate a custom token using Firebase Admin SDK.

gist.github.com