Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dataconnect not recgonizing Signed in user in Next.js API route #8797

Open
ignitehub-dev opened this issue Feb 16, 2025 · 6 comments
Open

Dataconnect not recgonizing Signed in user in Next.js API route #8797

ignitehub-dev opened this issue Feb 16, 2025 · 6 comments

Comments

@ignitehub-dev
Copy link

ignitehub-dev commented Feb 16, 2025

Operating System

macOS Monterey 12.7.4

Environment (if applicable)

Chrome 132

Firebase SDK Version

11.2.0

Firebase SDK Product(s)

DataConnect, Auth

Project Tooling

My project is typescript next.js version 15.1.6 using app router. It uses Firebase Auth, Functions and DataConnect. Project repo is public on my profile named "IgniteHub". Latest is pushed.

Detailed Problem Description

I have an auth context that I want to connect to an api route the will create a users profile in DataConnect when a new user signs in/up. It will check dataconnect for the users UID in my users table if the user doesnt exist it creates the profile with userCredentials from the return of signInWithPopup. When doing this DataConnect isnt reconizing the signed in user and giving me the error message.

authContext function

async function handleGoogleSignIn() {
		try {
			setLoading(true);
			const provider = new GoogleAuthProvider();
			const userCredential = await signInWithPopup(auth, provider);
                         const token = await userCredential.user.getIdToken();
			const userProfile = await fetch(
				"/api/users/user?" +
					new URLSearchParams({
						userId: userCredential.user.uid,
						displayName: userCredential.user.displayName as string,
						email: userCredential.user.email as string
					}).toString(),
					{
					  headers: {
						Authorization: `Bearer ${token}`,
					  },
					}
			);
			setUserProfile(userProfile)
			console.log("userProfile from api: ", userProfile);
			handleCloseModal();
			router.push("/dashboard");
		} catch (error) {
			console.error("Google sign-in error:", error);
		} finally {
			setLoading(false);
		}
	}

API Route

import { NextResponse } from "next/server";
import { getUserById, createUser } from "@IgniteHub/dataconnect";
import { dataConnect } from "@/utils/firebase";
import { z } from "zod";

const querySchema = z.object({
	userId: z.string(),
	displayName: z.string().optional(),
	email: z.string()
});

export async function GET(request: Request) {
	const { searchParams } = new URL(request.url);
	const query = Object.fromEntries(searchParams.entries());

	const parsed = querySchema.safeParse(query);
	if (!parsed.success) {
		return NextResponse.json(
			{ error: parsed.error.flatten() },
			{ status: 400 }
		);
	}

	const { userId, displayName, email } = parsed.data;

	try {
		const userProfile = await getUserById(dataConnect, { id: userId });
		if (!userProfile.data.user) {
			const [firstName, lastName = ""] = (displayName ?? "").split(" ");
			const userData = await createUser({
				id: userId,
				firstName,
				lastName,
				email
			});
		}
		const newUserProfile = await getUserById(dataConnect, { id: userId });
		return NextResponse.json({ userProfile: newUserProfile.data.user });
	} catch (error) {
		return NextResponse.json(
			{ error: "Failed to fetch user profile:" + error },
			{ status: 500 }
		);
	}
}

firebase.ts

import { initializeApp } from "firebase/app";
import { getAuth, connectAuthEmulator } from "firebase/auth";
import {
	getDataConnect,
	connectDataConnectEmulator
} from "firebase/data-connect";
import { connectorConfig } from "@ignitehub/dataconnect";
import { getFunctions, connectFunctionsEmulator } from "firebase/functions";
// import { getStorage } from "firebase/storage";

const firebaseConfig =
	process.env.NEXT_PUBLIC_ENV === "development"
		? {
				apiKey: "dev-api-key",
				authDomain: "localhost",
				projectId: "dev-project-id",
				storageBucket: "dev.appspot.com",
				messagingSenderId: "dev-sender-id",
				appId: "dev-app-id"
			}
		: {
				apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
				authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
				projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
				storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
				messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
				appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID
			};

export const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const functions = getFunctions(app);
export const dataConnect = getDataConnect(app, connectorConfig);
// export const storage = getStorage();

if (process.env.NEXT_PUBLIC_ENV === "development") {
	console.log("-- Dev mode detected connecting to emulators ---");
	connectAuthEmulator(auth, "http://localhost:9099");
	console.log("-- Connected to Firebase Auth emulator --");
	connectFunctionsEmulator(functions, "localhost", 5001);
	console.log("-- Connected to Firebase Functions emulator --");
	connectDataConnectEmulator(dataConnect, "localhost", 9399);
	console.log("-- Connected to Firebase DataConnect emulator --");
}

package.json

{
	"name": "client2",
	"version": "0.1.0",
	"private": true,
	"scripts": {
		"dev": "next dev --turbopack",
		"build": "next build",
		"start": "next start",
		"lint": "next lint"
	},
	"dependencies": {
		"@emotion/react": "^11.14.0",
		"@emotion/styled": "^11.14.0",
		"@fortawesome/fontawesome-svg-core": "^6.7.2",
		"@fortawesome/free-solid-svg-icons": "^6.7.2",
		"@fortawesome/react-fontawesome": "^0.2.2",
		"@IgniteHub/dataconnect": "file:ignitehub-dataconnect/js/default-connector",
		"@mui/icons-material": "^6.4.2",
		"@mui/material": "^6.4.2",
		"@mui/x-charts": "^7.26.0",
		"@mui/x-data-grid": "^7.26.0",
		"@mui/x-date-pickers": "^7.26.0",
		"@mui/x-tree-view": "^7.26.0",
		"@react-spring/web": "^9.7.5",
		"dayjs": "^1.11.13",
		"firebase": "^11.2.0",
		"firebase-admin": "^12.6.0",
		"next": "15.1.6",
		"react": "^18.0.0",
		"react-dom": "^18.0.0",
		"zod": "^3.24.2"
	},
	"devDependencies": {
		"@eslint/eslintrc": "^3",
		"@types/node": "^20",
		"@types/react": "^19",
		"@types/react-dom": "^19",
		"eslint": "^9",
		"eslint-config-next": "15.1.6",
		"prettier": "^3.4.2",
		"typescript": "^5"
	}
}

Error Message

[2025-02-16T05:36:16.149Z]  @firebase/data-connect: DataConnect (11.3.1): Error while performing request: {"code":16,"message":"unauthenticated: this operation requires a signed-in user","details":[]}
 GET /api/user?userId=Mmek1xVjHXQIhc1P3AvUHiftdgVC&displayName=Mountain+Mountain&email=mountain.mountain.867%40example.com 500 in 61ms

Steps and code to reproduce issue

  • run firebase emulators
  • build app with npm run dev
  • login UI has button that calls handleGoogleSignIn
  • get error message
@ignitehub-dev ignitehub-dev added new A new issue that hasn't be categoirzed as question, bug or feature request question labels Feb 16, 2025
@google-oss-bot
Copy link
Contributor

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

@maneesht
Copy link
Contributor

Hi @ignitehub-dev, have you tried using initializeServerApp? It seems like the server doesn't have the context of the client app: https://firebase.blog/posts/2024/05/firebase-serverapp-ssr/

@jbalidiong jbalidiong added needs-attention api: dataconnect and removed needs-triage new A new issue that hasn't be categoirzed as question, bug or feature request labels Feb 17, 2025
@ignitehub-dev
Copy link
Author

ignitehub-dev commented Feb 19, 2025

@dlarocque @maneesht Ahh havent seen the serverApp before.

I tried updating route.ts with the following but not entirely sure this is correct? It seems like its not recognizing the emulator configs?

	const authIdToken = request.headers.get("authorization")?.split(" ")[1];
	const serverApp = initializeServerApp(app, { authIdToken });
	const auth = getAuth(serverApp);
	await auth.authStateReady();
	const dataConnectServer = getDataConnect(serverApp, connectorConfig);
[2025-02-19T03:22:29.365Z]  @firebase/data-connect: DataConnect (11.3.1): Error while performing request: {"error":{"code":403,"message":"Firebase Data Connect API has not been used in project dev-project-id before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/firebasedataconnect.googleapis.com/overview?project=dev-project-id then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.","status":"PERMISSION_DENIED","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"SERVICE_DISABLED","domain":"googleapis.com","metadata":{"containerInfo":"dev-project-id","activationUrl":"https://console.developers.google.com/apis/api/firebasedataconnect.googleapis.com/overview?project=dev-project-id","service":"firebasedataconnect.googleapis.com","consumer":"projects/dev-project-id","serviceTitle":"Firebase Data Connect API"}},{"@type":"type.googleapis.com/google.rpc.LocalizedMessage","locale":"en-US","message":"Firebase Data Connect API has not been used in project dev-project-id before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/firebasedataconnect.googleapis.com/overview?project=dev-project-id then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry."},{"@type":"type.googleapis.com/google.rpc.Help","links":[{"description":"Google developers console API activation","url":"https://console.developers.google.com/apis/api/firebasedataconnect.googleapis.com/overview?project=dev-project-id"}]}]}}
 GET /api/user?userId=loG9f6q3ToSHjl6EZciIgvwrsTa6&displayName=Orange+Olive&email=orange.olive.311%40example.com 500 in 804ms

If I disable the emulators, I still get a 401 unauthenticated.

@ignitehub-dev
Copy link
Author

Actually think I wasnt connecting to the emulator with the server app.

Added

	connectDataConnectEmulator(dataConnectServer, "localhost", 9399);

But still getting 401 unauthenticated

@maneesht
Copy link
Contributor

@ignitehub-dev have you tried to pass in the dataConnectServer object to your generated SDKs?
For example:

listMovies(dataConnectServer);

@ignitehub-dev
Copy link
Author

@maneesht Yessir, full file below. It looks like my user isnt getting authenticated with the token? The Documentation you sent states that.

// FirebaseServerApp and Auth will now attempt
// to sign in the current user based on provided
// authIdToken.

But assuming im getting null either because im setting up a new auth instance or the user hasn't had time to get created in the emulator? But Wrapping my call in a setTimeout didnt help.

auth.currentUser:  null
FirebaseServerApp could not login user with provided authIdToken:  [Error [FirebaseError]: Firebase: Error (auth/invalid-user-token).] {
  code: 'auth/invalid-user-token',
  customData: {}
}
import { NextResponse } from "next/server";
import { getUserById, createUser } from "@IgniteHub/dataconnect";
// import { dataConnectServer } from "../firebaseServer";
import { app, dataConnect, firebaseConfig } from "@/utils/firebase";
import { initializeServerApp } from "firebase/app";
import { z } from "zod";
import { connectorConfig } from "@IgniteHub/dataconnect";
import {
	getDataConnect,
	connectDataConnectEmulator
} from "firebase/data-connect";
import { getAuth } from "firebase/auth";

const querySchema = z.object({
	userId: z.string(),
	displayName: z.string().optional(),
	email: z.string()
});

export async function GET(request: Request) {
	const authIdToken = request.headers.get("authorization")?.split(" ")[1];
	const serverApp = initializeServerApp(app, { authIdToken });
	const auth = await getAuth(serverApp);
	console.log("auth.currentUser: ", auth.currentUser);
	await auth.authStateReady();
	const dataConnectServer = getDataConnect(serverApp, connectorConfig);
	connectDataConnectEmulator(dataConnectServer, "localhost", 9399);
	const { searchParams } = new URL(request.url);
	const query = Object.fromEntries(searchParams.entries());

	const parsed = querySchema.safeParse(query);
	if (!parsed.success) {
		return NextResponse.json(
			{ error: parsed.error.flatten() },
			{ status: 400 }
		);
	}

	const { userId, displayName, email } = parsed.data;

	try {
		const userProfile = await getUserById(dataConnectServer, { id: userId });
		if (!userProfile.data.user) {
			const [firstName, lastName = ""] = (displayName ?? "").split(" ");
			const userData = await createUser(dataConnectServer, {
				id: userId,
				firstName,
				lastName,
				email
			});
			console.log("mutation data: ", userData);
		}
		return NextResponse.json({ message: "Success" });
	} catch (error) {
		return NextResponse.json(
			{ error: "Failed to fetch user profile:" + error },
			{ status: 500 }
		);
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants