DEV Community

loading...

A simple control panel for application admins to manage users and privileges using Firebase 

iamdoctorj profile image Jyotirmaya Sahu Originally published at Medium on ・7 min read

A simple control panel for application admins to manage users and privileges using Firebase.

Firebase admin SDK is a server side SDK that allows us to interact with our Firebase project with admin privileges and perform certain actions to monitor and manager our project in our own way without using the Firebase console.

We are going to create a web application through which we will try to perform the actions that the Firebase admin SDK provides us with.

For that purpose we will need a front-end application which will act as the control panel and a back-end where we will integrate the admin SDK.

We will walk-through the front-end in another Part.

This project is made using Ubuntu 20.04 OS.

Pre-requisites

Create a firebase project and turn on Authentication — email & password authentication, and Realtime Database.

Visit console.firebase.com to create a project and configure as above.

Part 1 — Making the back-end

We will be using node-js as the back-end for making a rest API that our front-end application will consume. We will be using Typescript as it provides us a better and error free way to write Javascript code.

In the first step we are going to set up a node project to use Typescript. We will use express for making the rest API.

After creating a new folder and opening the terminal, let’s run the commands to create the project.

npm init -y
Enter fullscreen mode Exit fullscreen mode

Okay, now we have a package.json file. Let’s install the required dependencies for our project.

npm install express cors dotenv firebase-admin
Enter fullscreen mode Exit fullscreen mode

Also typescript , tslint and the type declarations for cors and express as dev-dependencies.

npm install typescript tslint @types/express @types/cors
Enter fullscreen mode Exit fullscreen mode

Now let’s make a few changes to the package.json to really integrate typescript in our build process. We will add a “start” key to the scripts object as follows.

“start”: “tsc && node dist/index.js”

With this we are making sure that we are running the Typescript compiler (or tsc) to transpile all the .ts files before running the application. We will modify the .tsconfig file to mention the dist directory as the output directory for the typescript compiler later in the article.

Let’s mention the “dist/index.js” as the value of the main property as this file will be the entry point for our application.

With these changes, the package.json file should look similar to this.

Now, let’s add a tsconfig.json file to the project root with the following values. This file is a configuration file for typescript specific to that project. Here we mention the “outDir” as “dist” which makes sure that tsc uses the dist directory as the output directory for the transpiled files.

Now to configure Typescript linting for the project, in a terminal running in the root of the project, run the following command to generate tslint.json.

./node_modules/.bin/tslint --init
Enter fullscreen mode Exit fullscreen mode

Open the generated tslint.json file and the no-console rule accordingly.

Now lets start configuring firebase-admin sdk in our project. For initializing the firebase-admin sdk we need to configure a service account.

Follow this guide to configure service account, download the key and rename it as service-key.json. Place this file in the root of the project directory.

This file should not be pushed to any remote location where it is subject to the risk of getting exposed. This file should be added to the .gitignore file in case of git.

In this project we are using dotenv to simplify the task of setting and using environment variables in multiple environments. So, we will create a .env file at the project root to where we can define the several values that we require as environment variables.

Create a .env file and paste the following values:

GOOGLE_APPLICATION_CREDENTIALS=service-key.json

DB_URL=<Your DB URL>
Enter fullscreen mode Exit fullscreen mode

Find your DB URL in the firebase console on top of your realtime database.

Now, we will create a directory structure as shown:

src

 — models

—modules

 — — admin

 — — auth

 — — users

Create an index.ts file under the src directory.

In the index.ts file, let’s import the required modules.

import express from 'express';
import * as admin from 'firebase-admin';
import * as dotenv from 'dotenv';
import cors from 'cors';
Enter fullscreen mode Exit fullscreen mode

Now, before we initialize the admin sdk, we need to configure dotenv , in order to inject the values mentioned in the .env file as the environment variables.

const dotenvKey = dotenv.config();
Enter fullscreen mode Exit fullscreen mode

Here you can remove the constant assignment as we are not going to use the dotenvKey constant in the project.

Now, to initialize the firebase-admin sdk.

admin.initializeApp({    
    credential: admin.credential.applicationDefault(),    
    databaseURL: process.env.DB_URL
});
Enter fullscreen mode Exit fullscreen mode

Here, firebase-admin will use the environment variable mapped by us in the .env file to access the service-key.json file. Also, we provide the databaseURL as our application will access the realtime database.

Now, let’s add the boilerplate code to create an express app.

**const** port = process.env.PORT || 8080;
**const** app = _express_(); 
app._use_(cors({origin: true}));
app._use_(express._json_());
app._use_(express._urlencoded_({extended: false}));
Enter fullscreen mode Exit fullscreen mode

We use cors to avoid the CORS error while consuming endpoints from a browser.

A great thing about Typescript is that it allows the use of Interfaces, which we can use in this case to define a schema for our user model. So, under the models directory, we will create a file named UserModel.ts , with the content as:

export interface User {
    uid: string,
    email: string | undefined,
    displayName: string | undefined,
    lastSignInTime: string,
    creationTime: string,
    emailVerified: boolean,
    admin: boolean
}
Enter fullscreen mode Exit fullscreen mode

Before proceeding with creating the routes, we need to secure the endpoints to prevent unauthorized access. For this purpose, we will create and use two middlewares, one for Authentication and another one for Authorization, i.e, to ensure whether the user requesting the operation has sufficient privileges.

Let’s create two files, authenticate.middleware.ts , authorize.middleware.ts.

We will use authenticate to check if user has a valid idToken, and the authorize to check if the user has the required privileges.

Note: The authenticate filter requires an Authorization header with the value in the following form:- Bearer

Note: The idToken can be retrieved by signing-in to firebase using the REST API for firebase Authentication for the moment.

Proceeding further, we need to create a UsersController.ts file under modules →users that will contain the methods that our routes for “/users” path will utilize.

Here, “//RA” means the particular function requires admin privileges to perform its functionality.

This name of the methods define its functionality in this case.

getAllUsers() →Returns a list of all the users.

getUser() →Accepts an uid and returns the particular user.

updateUser() →Here we can perform many task, but in the current scope we will only set the displayName property of the user. You can play around with the function and find out all the fields that a user can have in firebase authentication.

createUser() →Creates an user.

createAdminUser() →Creates an user but with Admin privileges.

deleteUser() →Deletes the user whose uid is provided.

deleteUsers() →Deletes multiple users, uid array is fetched from body.

forceSignOut() →Forcibly revoke a users refresh token, so that their login can’t continue.

mapUserFromUserRecord() →An utility function, private to the file, which helps in extracting user from an UserRecord object returned by the admin sdk, returns a User _ object._

Here, we use customClaims to store the admin role for an user. These customClaims can only be retreived through the admin SDK. (I didn’t find any way to retrieve them from the client sdk, Kindly mention in the feedback if I am wrong.)

We need a router that can map different routes to the functions. For that purpose we create a file routes.ts in the same directory. The file contains the following.

Here, we define a configureRoutes function, which will take our express app and add the routes. We pass true to authorize where the route requires admin privilege to perform the task.

Similarly, we create two files, adminController.ts and routes.ts under modules →admin.

Finally, we add the following import statements to the index.ts _ _file.

import { configureRoutes as configureRoutesForUsers } from './modules/users/routes';
import { configureRoutes as configureRoutesForAdmin } from './modules/admin/routes';
Enter fullscreen mode Exit fullscreen mode

We listen for the endpoints by starting the server using the following piece of code:

app.listen( port, () => {
    console.log('Server listening at port '+ port);
});
Enter fullscreen mode Exit fullscreen mode

Finally start the server on localhost using

npm start
Enter fullscreen mode Exit fullscreen mode

We can use Postman to test the endpoints. Ex: http://localhost:8080/users/.

We can add users to the project by using the firebase console or by using the REST API for firebase. And thereafter we can retrieve the uid from the firebase console.

Note: For the first time, to add an admin, we can normally add an user and call the /admin/:uid/make endpoint by passing false to its authorize filter. After that we can revert the value to true.

Kindly refer to the following Github repository for the complete project files.

i-am-jyotirmaya/Firebase-admin-demo-api

Thank You

Discussion (0)

pic
Editor guide