Kinde with Firebase

By Vlad Ionash — Published

Kinde with Firebase

Link to this section

Integrating Firebase with Kinde Auth in a simple implementation can involve several steps and code configurations, depending on the specific features you wish to use from each service. For this example, we’ll focus on a scenario where you use Kinde Auth for authentication and Firebase for accessing a database service (Firestore) post-authentication.

This walkthrough assumes:

  • you have configured a Kinde account
  • you have already set up a Kinde project (a demo project/starter kit is fine)
  • you have already set up a Firebase project

This will showcase a web application using Firebase SDK 9 (modular) and Kinde Auth Pages Router, demonstrating how a user would sign in and then fetch data from Firestore as an authenticated user.

Step 1: Install Firebase SDK

Link to this section

First, you’ll need to add Firebase to your project. Run the following command in your project directory:

npm install firebase

For developers looking to integrate Firebase services, including Firestore, into their projects, the starting point is the Firebase Documentation which should cover everything from initial setup to advanced features utilization.

Step 2: Configure Firebase

Link to this section

Create a Firebase project in the Firebase Console, register your web app, and get your Firebase configuration object.

Create a Firebase Project in the Firebase Console

Link to this section
  1. Navigate to the Firebase Console
  2. Create a New Project
    • Click on “Add project.”
    • Enter a project name. This name can be anything meaningful related to your web app.
    • (Optional) You can edit the Project ID, which is a unique identifier for your project.
    • Accept the Firebase terms and conditions.
    • Click on “Continue.”
  3. Configure Google Analytics (Optional)
    • You’ll be prompted to configure Google Analytics for your Firebase project. This step is optional but recommended for analyzing your app’s usage data.
    • If you choose to enable Google Analytics, follow the on-screen instructions to either select an existing Google Analytics account or create a new one.
    • Once configuration is complete, click on “Create project.”
  4. Project Created
    • After a few moments, your Firebase project will be created. Click “Continue” to move into your project’s dashboard.

Register Your Web App

Link to this section
  1. Add Your Web App to the Project
    • On the Firebase Console dashboard, you will see an icon for the web (</>). Click on it to add your web app.
  2. Register Your App
    • Enter a name for your web app.
    • (Optional) If you plan to use Firebase Hosting, you can check the box to also set up Firebase Hosting for this app.
    • Click on “Register app.”

Get Your Firebase Configuration Object

Link to this section
  1. Firebase SDK Setup

    After registering your app, you will be presented with your Firebase SDK snippet. This contains the Firebase configuration object which looks something like this:

    const firebaseConfig = {
        apiKey: "YOUR_API_KEY",
        authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
        projectId: "YOUR_PROJECT_ID",
        storageBucket: "YOUR_PROJECT_ID.appspot.com",
        messagingSenderId: "YOUR_SENDER_ID",
        appId: "YOUR_APP_ID",
        measurementId: "YOUR_MEASUREMENT_ID"
    };
    

    In your Next.js project, create a file named firebaseConfig.js (you can place it in a lib or utils directory) and add your Firebase project’s configuration:

    // firebaseConfig.js
    
    import {initializeApp} from "firebase/app";
    import {getFirestore} from "firebase/firestore";
    
    const firebaseConfig = {
        apiKey: "YOUR_API_KEY",
        authDomain: "YOUR_AUTH_DOMAIN",
        projectId: "YOUR_PROJECT_ID",
        storageBucket: "YOUR_STORAGE_BUCKET",
        messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
        appId: "YOUR_APP_ID"
    };
    
    // Initialize Firebase
    const app = initializeApp(firebaseConfig);
    
    // Initialize Firestore
    const db = getFirestore(app);
    
    export {db};
    

Now you’ve successfully created a Firebase project, registered your web app, and integrated the Firebase configuration into your project!

Step 3: Using Firestore in Your Application

Link to this section

After a user is authenticated with Kinde, you can interact with Firestore. For example, you might want to fetch user data from Firestore in a server component or API route.

Fetching Data from Firestore

Link to this section

Let’s say you have a collection named posts in Firestore, and you want to display these posts on a page in your Next.js app.

Fetching Data for Static Generation (getStaticProps)

// pages/posts.js
import {db} from "../lib/firebaseConfig";
import {collection, getDocs} from "firebase/firestore";

export default function Posts({posts}) {
    return (
        <div>
            <h1>Posts</h1>
            <ul>
                {posts.map((post) => (
                    <li key={post.id}>{post.title}</li>
                ))}
            </ul>
        </div>
    );
}

export async function getStaticProps() {
    const postsCol = collection(db, "posts");
    const postsSnapshot = await getDocs(postsCol);
    const posts = postsSnapshot.docs.map((doc) => doc.data());

    return {
        props: {
            posts
        }
    };
}

In this example, getStaticProps fetches all documents from the posts collection in Firestore. The data is then passed as props to the Posts component, which renders a list of post titles.

Fetching Data for Server-Side Rendering (getServerSideProps)

If your data needs to be fetched at request time, for instance, to ensure it’s always up-to-date, you can use getServerSideProps instead.

// pages/posts.js
import {db} from "../lib/firebaseConfig";
import {collection, getDocs} from "firebase/firestore";

export default function Posts({posts}) {
    return (
        <div>
            <h1>Posts</h1>
            <ul>
                {posts.map((post) => (
                    <li key={post.id}>{post.title}</li>
                ))}
            </ul>
        </div>
    );
}
// replace getStaticProps with getServerSideProps
export async function getServerSideProps() {
    const postsCol = collection(db, "posts");
    const postsSnapshot = await getDocs(postsCol);
    const posts = postsSnapshot.docs.map((doc) => doc.data());

    return {
        props: {
            posts
        }
    };
}

Adding Data to Firestore

Link to this section

Let’s create a simple form that allows users to add new posts to the Firestore posts collection.

1. Create a Form Component

// components/PostForm.js
import {useState} from "react";
import {db} from "../lib/firebaseConfig";
import {collection, addDoc} from "firebase/firestore";

export default function PostForm() {
    const [title, setTitle] = useState("");

    const handleSubmit = async (e) => {
        e.preventDefault();
        try {
            const docRef = await addDoc(collection(db, "posts"), {
                title
            });
            console.log("Document written with ID: ", docRef.id);
            setTitle(""); // Reset the form field after submission
        } catch (error) {
            console.error("Error adding document: ", error);
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <input
                type="text"
                value={title}
                onChange={(e) => setTitle(e.target.value)}
                placeholder="Post title"
            />
            <button type="submit">Add Post</button>
        </form>
    );
}

2. Use the Form Component in Your Page

// pages/add-post.js
import PostForm from "../components/PostForm";

export default function AddPostPage() {
    return (
        <div>
            <h1>Add New Post</h1>
            <PostForm />
        </div>
    );
}

In this example, the PostForm component contains a form with a single input field for the post title. When the form is submitted, it calls addDoc to create a new document in the posts collection with the title provided. After successful submission, the form field is cleared.

// pages/api/userData.js

import {db} from "../../lib/firebaseConfig";
import {doc, getDoc} from "firebase/firestore";

export default async function handler(req, res) {
    // Assume you have the user's ID stored in a session or passed in the request
    const userId = req.query.userId;

    const docRef = doc(db, "users", userId);
    const docSnap = await getDoc(docRef);

    if (docSnap.exists()) {
        res.status(200).json(docSnap.data());
    } else {
        res.status(404).json({message: "No such document!"});
    }
}

Step 4: Protecting Routes and Accessing Firestore After Authentication

Link to this section

You can protect your routes using Kinde Auth as described in the documentation. Once a user is authenticated, you can use their information to query Firestore or perform other operations. This might involve passing the user’s ID or other identifying information to Firestore queries to fetch or update user-specific data.

Protecting a Page and Fetching Data from Firestore

Link to this section

Let’s create a protected page that only authenticated users can access. This page will fetch user-specific data from Firestore based on the user’s ID or email obtained from Kinde Auth after successful authentication.

Creating a Protected Page with Server-Side Rendering (SSR)

Link to this section

In this example, we’ll protect a profile page (pages/profile.js) and fetch user profile data from Firestore.

// pages/profile.js
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";
import {db} from "../lib/firebaseConfig";
import {doc, getDoc} from "firebase/firestore";

export default function Profile({userData}) {
    if (!userData) {
        return <div>You must be logged in to view this page.</div>;
    }

    return (
        <div>
            <h1>User Profile</h1>
            <p>Name: {userData.name}</p>
            <p>Email: {userData.email}</p>
            {/* Display more user data as needed */}
        </div>
    );
}

export async function getServerSideProps(context) {
    const {isAuthenticated, getUser} = getKindeServerSession(context.req);

    if (!(await isAuthenticated())) {
        return {
            redirect: {
                destination: "/api/auth/login", // Redirect user to login if not authenticated
                permanent: false
            }
        };
    }

    const user = await getUser();
    const userEmail = user.email; // Assuming you store user email in Kinde Auth

    // Fetch user data from Firestore based on email
    const usersRef = doc(db, "users", userEmail);
    const docSnap = await getDoc(usersRef);

    if (!docSnap.exists()) {
        return {props: {userData: null}}; // Handle case where user data does not exist
    }

    return {
        props: {userData: docSnap.data()} // Pass user data as props to the page
    };
}

In this example, getServerSideProps uses Kinde Auth to check if the user is authenticated. If not, it redirects the user to the login page. If the user is authenticated, it fetches the user’s profile data from Firestore using the user’s email as the key. This data is then passed as props to the Profile component to render the user’s profile information.

Creating an API Route to Update Firestore Data

Link to this section

Now, let’s create an API route that allows authenticated users to update their profile data stored in Firestore.

// pages/api/updateProfile.js
import {db} from "../../lib/firebaseConfig";
import {doc, updateDoc} from "firebase/firestore";
import {getKindeServerSession} from "@kinde-oss/kinde-auth-nextjs/server";

export default async function handler(req, res) {
    if (req.method !== "POST") {
        return res.status(405).end(); // Method Not Allowed
    }

    const {isAuthenticated, getUser} = getKindeServerSession(req);

    if (!(await isAuthenticated())) {
        return res.status(401).json({message: "Not authenticated"});
    }

    const user = await getUser();
    const userEmail = user.email; // Assuming you store user email in Kinde Auth

    const {name, ...otherProfileData} = req.body;

    const userRef = doc(db, "users", userEmail);

    try {
        await updateDoc(userRef, {
            name,
            ...otherProfileData
        });
        res.status(200).json({message: "Profile updated successfully"});
    } catch (error) {
        res.status(400).json({message: "Error updating profile", error: error.message});
    }
}

This API route (/api/updateProfile) allows authenticated users to send a POST request with their new profile data, which updates their corresponding document in Firestore. The route first checks if the user is authenticated using Kinde Auth. If the user is authenticated, it updates the user’s profile data in Firestore using the user’s email as the key.

⚠️ Remember to secure your Firestore data with appropriate security rules, ensuring that users can only access data they are authorized to view or modify. You should match the Kinde user permissions with Firestore’s appropriate security rules!

Congratulations 🎉

Link to this section

This example provides a basic overview of integrating Kinde Auth with Firebase. Depending on your application’s specific needs, you might need to adjust the flow, especially regarding handling user sessions and securely passing user information between your frontend and backend services.

Remember to observe good security practices in handling user data and credentials. Regularly update your dependencies and monitor your application for any security advisories.

If you need any assistance with getting Kinde connected reach out to support@kinde.com.

You can also join the Kinde community on Slack or the Kinde community on Discord for support and advice from the team and others working with Kinde.