Photo by Ricardo Gomez Angel on Unsplash

Firebase and Firebase-admin in a single SvelteKit App

Ahmad Syarif
6 min readSep 28, 2023

--

I recently built an app for a friend whose business is a travel agency specializing in Umrah or the pilgrim to Mecca for Muslims. He provides service to his customers where his customer can deposit their money in his business and when the amount is enough, his customers will get the chance to visit the holy city.

The app was meant to help customers track their balance and see their historical deposits and withdrawals. it was meant to create transparency between the business and the customers.

So I charged it for free and built it over the weekend.

I’ve been using Svelte and Firebase for the last half year. I’m quite familiar with the integration of Firebase Client SDK with Svelte. But for this project, I need Firebase admin because my friend asks that only he can create an account for his customer.

Since it’s kind of an interesting topic for me, I decided to share it here so you can learn it as well. I won’t touch on how to initialize the Firebase app itself in this article, but I can write it down for you if you want.

Firebase Admin and Client SDK

First, we need to understand the problem. Firebase has two sets of SDK

  1. Firebase Client SDK
  2. Firebase Admin SDK

Both SDKs allow you to interact with Firebase but in a different environment. Firebase Client SDK was meant to be used for client applications such as websites and mobile applications. The Client SDK has limited access and is initialized with an API key which is a non-secret value. Firebase Admin SDK itself is meant for backend service where it can access more functionality, such as adding users and bypassing Firestore rules, and is initialized with a service account containing a private key.

Initializing Firebase Client

Let’s start with how to integrate Firebase Client SDK. First, let’s install the Firebase Client SDK into your SvelteKit Application

npm i -D firebase

I’d like to create a dedicated file for managing the Firebase Client SDK so let’s create a file lib/firebase/firebase.app.ts and initialize the Client SDK

import { initializeApp,getApps,getAuth, type FirebaseApp } from 'firebase/app';
/**
* Your firebase client SDK config goes here
*/
const config = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: ""
}

let firebaseApp:FirebaseApp | undefined;
let firebaseAuth:Auth;
// create singleton of firebase client app
if(!getApps().length){
firebaseApp = initializeApp(config);
}
else{
firebaseApp = getApps()[0]
}

firebaseAuth = getAuth(firebaseApp);
// export the firebase app
export {firebaseApp,firebaseAuth}

you can now use the firebaseApp across the application. For example for auth listener in the root +layout.svelte

<script lang="ts">
import { onMount } from "svelte";
import "../app.css";
import { firebaseAuth } from "$lib/firebase/firebase.auth";

onMount(() => {
firebaseAuth.onAuthStateChanged(async(user) => {

});
});
</script>


<slot></slot>

Bear in mind that you have to make sure the firebaseApp is used in the browser context. you can use browser, onMount, or export const ssr=false to make sure that firebaseApp is only used in the front end.

Initializing the Firebase Admin SDK

First, install the Firebase Admin SDK into your SvelteKit app.

npm i -D firebase-admin

after that, create .env file at the root folder of your project and define variable FIREBASE_ADMIN_KEY or other name you wish

FIREBASE_ADMIN_KEY={}

FIREBASE_ADMIN_KEY contains the service account information that you can get from Firebase Console. it’s good to define it as an environment variable like this because you can define the same variable later for production in a platform like Vercel, .env is also ignored by default in the SvelteKit repository so you can keep the service account safe and not accidentally add it to your repository.

after that, create a dedicated file to initialize the Firebase Admin SDK such as lib/firebase/firebase.admin.ts

//Import Firebase Admin Service Account with $env functionality in Svelte
import {FIREBASE_ADMIN_KEY} from '$env/static/private'
//Import firebase admin SDK
import admin, { auth } from "firebase-admin"

var firebaseAdmin:admin.app.App
var firebaseAdminAuth:admin.auth.Auth
/**
* create firebase admin singleton
*/
function getFirebaseAdmin():admin.app.App{
if(!firebaseAdmin){
if(admin.apps.length == 0){
firebaseAdmin = admin.initializeApp({

credential:admin.credential.cert(JSON.parse(FIREBASE_ADMIN_KEY))
})
}
else{
firebaseAdmin = admin.apps[0]!;
}

}

return firebaseAdmin;
}
/**
* create firebase admin auth singleton
*/
function getFirebaseAdminAuth():admin.auth.Auth{
const currentAdmin:admin.app.App = getFirebaseAdmin();
if(!firebaseAdminAuth){
firebaseAdminAuth = currentAdmin.auth()
}
return firebaseAdminAuth;
}

after that, you can use the Firebase admin for a dedicated API in your SvelteKit app. For example, I use it to create API to create new users where only Admin can perform the action in a file routes/api/user/+server.ts

import { getFirebaseAdminAuth, getFirebaseAdminStore } from "$lib/firebase/firebase.admin";
import { error } from "@sveltejs/kit";
import type { RequestEvent } from "./$types";

export async function POST(request:RequestEvent){
console.log("create new user");
const {email,password,name,phone} = await request.request.json();
const auth = getFirebaseAdminAuth();

//verify that the requester is administrator
const token = request.cookies.get("token")!
const decodedToken = await auth.verifyIdToken(token);
/**
* You can perform additional logic here to determined a admin or not, such as reading from firestore or read custom uid
*/

//create user
const userRecord = await auth.createUser({email:email,password:password});
/**
* you can perform additional operation here like creating firestore document for the new user
*/

return new Response(JSON.stringify({id:userRecord.uid}));

}

Handling the user token

if you notice in the routes/api/user/+server.ts, there is code where we get the user token from cookies

const token = request.cookies.get("token")!

This is the main problem I faced when I originally tried to integrate the Firebase Admin. How can I transfer the user token to the backend service?

In Angular (my previous love), you can intercept all HTTP requests and add an Authorization header with the user token for requests targeting your backend services. But SvelteKit does not have that functionality.

We solve this by using cookies. But cookies are set by the server not the client, and the user token is generated on the client side not on the server side.

The solution is to send the token to the server every time it’s generated and the server will set the cookies based on the token.

First, create the API for handling the token in routes/server/auth/+server.ts

import { getFirebaseAdminAuth } from "$lib/firebase/firebase.admin";
import type { RequestEvent, RequestHandler } from "@sveltejs/kit";

export async function POST(request:RequestEvent){
const {token} = await request.request.json();

//delete the previous token in cookies
request.cookies.delete('token',{path:"/"})

const auth = getFirebaseAdminAuth();
try{
//decode the token to make sure it's valid, you can add additional logic here
const decodedToken = await auth.verifyIdToken(token)

//set the token with path option to make the token accessible in all API paths
request.cookies.set('token',token,{path:"/"});
}
catch(e){
console.log(`error verifying ID Token : ${e}`);
}

return new Response();
}

after that, we have to modify the auth listener in the root +layout.svelte so that it will send the token every time it’s regenerated

<script lang="ts">
import { onMount } from "svelte";
import "../app.css";
import { firebaseAuth } from "$lib/firebase/firebase.auth";


onMount(() => {
// Listen to token generation
firebaseAuth.onIdTokenChanged(async (user)=>{
if(user){
//send the token to the server
const token = await user.getIdToken();
await fetch("/api/auth",{
method:'POST',
body:JSON.stringify({"token":token})
})
}
})
firebaseAuth.onAuthStateChanged(async(user) => {

});
});
</script>


<slot></slot>

With that, you can now make HTTP requests to your API without worrying about the token.

Conclusion

Particularly on handling the token, you can also make the login process by sending the email and password to the server and generating the token on the server side. But I don’t like this approach as it will limit the feature you can access on the Firestore Client SDK.

If you find this article helpful, consider following me on Medium and Twitter @asyarifstudio

--

--